Skip to content

Authority per namespace: genesis, roles, grants; scopes read inside it; authority shapes per namespace (issue 5) - #159

Open
Hafeok wants to merge 3 commits into
claude/great-feynman-zcdsdd-step-04-supersedesfrom
claude/great-feynman-zcdsdd-step-05-authority
Open

Hafeok wants to merge 3 commits into
claude/great-feynman-zcdsdd-step-04-supersedesfrom
claude/great-feynman-zcdsdd-step-05-authority

Conversation

@Hafeok

@Hafeok Hafeok commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator

Closes #148 — PRD ledger/prd/namespace-independence-prd.md §6 issue 5 (§3.2, §3.9; rulings 47, 67, 68 and 50 per namespace; AC-47 first bullet, AC-48, AC-68).

Stacked on #158 (issue 4). Review the last commit alone, 4ebe3658.

What lands

A grant, grant acceptance, interval, revocation or role belongs to the namespace whose directory holds it, and nothing in one namespace's authority has effect in another (LP-6.31).

  • Authority::of(store, ns) / Authority::as_of(store, landing, pos, ns) read one namespace's records; every role check, genesis lookup and policy verdict goes through them (the verbs' authorized, A006 over history, ruling 50's genesis-role rules, the reference rules, the unbound-genesis notice — now one per namespace).
  • A scope is read inside its own namespace: * and ns:<own> are the whole of it (ruling 67); a grant scoped ns:<other> than its directory is a SCHEMA fault.
  • A003/A005 count one trust root per namespace: the authority shapes run over each namespace's graph; the export selection keeps a namespace's own authority only (the * reach of LP-9.11 goes). Roles are per namespace (Store::role_in).
  • Writer: init --namespace opens every namespace with its own genesis grant, root and accept roles and first policy; --external-ref is required every time; join_genesis goes. grant new --namespace names where a grant is filed (inferred from a ns: scope, else the store's one namespace). The grant verbs move to author/grant_ops.rs.

Three deviations, stated

  1. Part of PRD issue 8 lands here. With authority per namespace, a later namespace's policy under another namespace's genesis fails A006, so the writer's own gate would refuse opening any second namespace between issues 5 and 8. Issue 8 keeps the key-close-in-every-namespace half (AC-47 third bullet).
  2. The decision shapes stay over the store's graph. PRD §3.9 says the whole graph stage runs per namespace; run that way, a supersedes edge in history into another namespace would be a permanent G001, against ruling 75 (live claims only), and based_on crosses by design. So A003/A005 run per namespace, G001–G006 over the store. LP-8.19 says so.
  3. Keys stay store-wide in this step (bound, closed and trusted across namespaces; init still carries the holder's key into a later namespace). Authority documents it; issue 6 scopes them. AC-47's second bullet (a rotate in A leaves B unchanged) therefore belongs to issue 6 and is not claimed here.

Tests

New ledger-cli/tests/authority_namespaces.rs (each failing before): two namespaces with their own genesis, roles and policy verify with no A003/A005; a later init needs its own mandate, and a namespace is opened once; a * grant in A authorises in A alone, and with two namespaces grant new needs --namespace; a grant scoped to another namespace is refused by the verb and a schema fault by hand; no grant carries a namespace field and authority records sit apart from decisions (AC-48); a store whose second namespace was put under the first's genesis, as the writer filed one before this, fails A006 on its policy and D7 on its first binding (AC-68); and the carried test — no agent identity and no non-interactive session produces an acceptance in a later namespace (authority.rs keeps the first-namespace one).

Rewritten, and why:

  • graph/export_tests.rs: the * genesis no longer reaches every namespace's export; another namespace's * grant and same-id role stay out.
  • signing/check_tests.rs, verify/order_grid.rs: filed fixture.ledger entities in a hafeok.ledger change-set; now one namespace.
  • ledger-cli/tests/authority.rs grantor test: ns:fixture.other as "another scope" would now be a grant made in fixture.other's authority, where the role is not declared; asserts that, keeps * and set:.
  • The pre-policy-binding cases rested on a later namespace whose genesis was older than its policy, which the writer can no longer file. New hand::found founds a namespace by hand with its genesis ten seconds older than its first policy; pre_policy_binding.rs and binding_accounting.rs use it. Two tests change meaning and are renamed: a skewed add before init's binding is now before the genesis too, so D7 refuses it; opening a namespace over a binding filed before any policy no longer turns it into L011 — it stays a schema fault, D7's.
  • closed_key.rs: the binding vouched by the closed key is dated after the second namespace's policy (before it, it is D7's whatever signs it); the point — the closed key vouches for nothing — stands.
  • Step 4's crossing.rs no longer asserts the absence of G001.

Protocol

LP-6.5, LP-6.16, LP-6.28, LP-6.31 lose their marks and notes (LP-6.31 keeps a note on keys until issue 6); LP-5.19, LP-8.19 (and the A005 row), LP-8.23, LP-8.31 (report shape: genesis_unbound is now one entry per namespace), LP-9.11, LP-8.8; Appendix B row; Appendix C note "Authority is per namespace". CLAUDE.md's authority paragraph updated. No digest moves; no format bump; no class added; no grant payload gains a field.

Checks

cargo build, cargo t, cargo clippy --workspace -- -D warnings -D clippy::unwrap_used, the quality scripts, and ledger verify --export on this repository (same output as on main).

🤖 Generated with Claude Code

https://claude.ai/code/session_01GgRcHXmnAu43hChNxhnqxU


Generated by Claude Code

Hafeok commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

CI, standing down on one step. The check job fails at the contract-surface gate (ddd diff-contracts <base>..HEAD): 18 undischarged exposed-surface changes, all this PR's — Authority::of added and as_of re-signed, Store::role_in for role, GrantArgs.namespace, the grant verbs moved to author/grant_ops.rs, references::faults and structure::entry_faults re-signed, GenesisUnbound's shape, and the test helper hand::found. Every other step of check passed (build, tests, clippy, ledger verify --export with the same output as main, ddd validate).

The remedy the gate names is ddd bind claude/great-feynman-zcdsdd-step-04-supersedes..HEAD (with --verdict), which files seam declarations under .ddd/seams/. The session's instructions forbid me to edit .ddd/, and a declaration's verdict is the author's judgment, so I am not filing them. Please run ddd bind on this branch and push, or tell me to. Recorded in the close-out.


Generated by Claude Code

@Hafeok
Hafeok force-pushed the claude/great-feynman-zcdsdd-step-04-supersedes branch from 12123c6 to ec43481 Compare October 11, 2026 06:15
@Hafeok
Hafeok force-pushed the claude/great-feynman-zcdsdd-step-05-authority branch from 4ebe365 to 73f98fb Compare October 11, 2026 06:17
Hafeok added a commit that referenced this pull request Oct 11, 2026
…two notices

Issue #149 (PRD issue 6, §3.3; rulings 47 and 69; AC-47 second bullet).
A key is bound, closed and trusted in one namespace. `Authority::of` and
`as_of` now filter key bindings by namespace like every other record;
`key_close::same_key` compares the namespace, so a close ends the key in
its own namespace only and `refusal`'s three rules (another principal's,
closed, already open) look at the binding's namespace; D7 (`filing`)
drops the store-wide branch — the genesis holder's first key in a
namespace is that namespace's self-bound binding, once per namespace,
under its own mandate; the signing check drops `carried_over`, judges a
first policy by a key trusted in its own namespace, and the re-acceptance
deadline runs from the key's earliest close there; `allowed_signers` ends
a line at a close in its own namespace. The writer binds the configured
key self-bound at every `init --namespace` (`carry_genesis_key` and
`no_live_key` go; `--without-key` stays until issue 13), `identity add`'s
self-bound test is per namespace, and signing refuses a key not live in
the namespace (`ToSign.any_namespace` goes). Two repository notices,
never findings (`verify::key_notices`, in the text and JSON report): a
key closed in one namespace and open in another; one key bound to two
principals in two namespaces.

Tests (each failing before the change): `key_across_namespaces.rs`,
rewritten as §3.11 says — bound in A then B, or B then A, a close in A
leaves B open: the key still signs there, A's `allowed_signers` line ends
and B's does not, the split is a notice, the key signs nothing in A, and
a third namespace opens with the same key self-bound; a rotate in A
leaves B's acceptances, review items and `allowed_signers` byte-identical
(AC-47, second bullet); the within-namespace rules of 2026-10-06 stand
(four tests kept); and the carried test, that no agent identity and no
non-interactive session produces a key close. The writer's close in
every namespace held is issue 8's and is not claimed. `genesis_key.rs`:
`init` in a later namespace self-binds the holder's key there under the
namespace's own mandate and signs the first policy with it; with every
key closed elsewhere `init` binds the key again and `verify` notices the
split (JSON `key_split`); with no usable key, `--without-key` initialises
unbound and the unbound-genesis notice names the namespace. The prompt
assigned these rewrites to issue 8; they could not wait, since the
cross-namespace lean they asserted is what this step removes.
`key_close_tests.rs`: three tests now assert the namespace reading.
The pre-policy cases now bind the holder's first key in the second
namespace self-bound: `pre_policy_binding.rs` (`unsigned_before` is
self-bound under `hand::found`'s mandate; the signed case signs the first
policy with the key bound before it, as LP-4.31 now requires; opening a
namespace over a binding filed in it before any policy is refused again,
since the holder's own self-bound binding would no longer be the first
there — `…_and_init_is_refused_over_it`); `binding_accounting.rs`
(`self_bound_by_hand`, one namespace per first binding). `trust.rs`:
`init` self-binds in the second namespace and a further key there is the
holder's own add (renamed from "once per store"); a hand-written
self-bound binding beside `init`'s fails, "first in the namespace".
`genesis_key.rs`'s forged-binding test reads the same wording.
`key_across_namespaces.rs` keeps its four within-namespace tests.

Protocol: LP-4.12, LP-4.13, LP-4.31, LP-4.32, LP-4.37, LP-4.38, LP-4.39
(ruling 101's wording), LP-6.31 (keys note dropped), LP-6.32 (loses its
mark; the writer's close-everywhere noted for issue 8), LP-8.31 (two
notices, report shape); Appendix B row; Appendix C note "Keys are per
namespace". CLAUDE.md updated. No class added; no digest moves (a
binding's hashed fields are unchanged and `namespace` was among them).

Stacked on #159 (issue 5).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GgRcHXmnAu43hChNxhnqxU
Hafeok and others added 3 commits October 11, 2026 06:22
…tage

Issue #148 (PRD issue 5, §3.2, §3.9; rulings 47, 67, 68 and 50 per
namespace; AC-47 first bullet, AC-48, AC-68). A grant, grant acceptance,
interval, revocation or role belongs to the namespace whose directory
holds it, and nothing in one namespace's authority has effect in another.

`Authority::of(store, ns)` and `Authority::as_of(store, landing, pos, ns)`
read one namespace's records (roles under its `roles/`, the records in
its directory); every role check, genesis lookup and policy verdict goes
through them: the verbs' `authorized`, `A006` over history, the
genesis-role rules of ruling 50, the cross-entry reference rules, the
unbound-genesis notice (one per namespace). A scope is read inside its
own namespace: `*` and `ns:<own>` are the whole of it, and a grant scoped
`ns:<other>` than its directory is a `SCHEMA` fault. The graph stage runs
the shapes over each namespace's graph alone, so `A003` and `A005` count
one trust root per namespace; for that the export selection keeps a
namespace's own authority records only — the `*` reach of LP-9.11 goes.
Roles are per namespace (`Store::role_in`; a duplicate id is judged
within the namespace).

The writer opens every namespace as it opens the first: `init
--namespace` bootstraps the namespace's own genesis grant, root and
accept roles and first policy, and `--external-ref` is required every
time; `join_genesis` goes. This part of PRD issue 8 lands here because
with authority per namespace a later namespace's policy under another
namespace's genesis fails `A006`, so the writer's own gate would refuse
opening any second namespace until issue 8. `grant new --namespace` names
where a grant is filed (inferred from a `ns:` scope, else the store's one
namespace); `grant accept|revoke` and the intervals read the grant's
directory. The grant verbs move to `author/grant_ops.rs` (file length).

Keys stay store-wide in this step — bound, closed and trusted across
namespaces, the genesis holder's key carried into a later namespace by
`init` as before — and `Authority` says so; issue 6 scopes them. AC-47's
second bullet (a rotate in A leaves B unchanged) therefore belongs to
issue 6 and is not claimed here.

Tests (each failing before the change): `ledger-cli/tests/authority_namespaces.rs`
— two namespaces each with their own genesis, roles and policy verify
with no `A003`/`A005`; a later `init` needs its own mandate and a
namespace is opened once; a `*` grant in A authorises in A alone and a
second namespace needs `--namespace`; a grant scoped to another
namespace is refused by the verb and a schema fault by hand; no grant
carries a namespace field and authority records sit apart from decisions
(AC-48); a store whose second namespace was put under the first's
genesis, as the writer filed one before this, fails `A006` on its policy
and D7 on its first binding (AC-68); and the carried test: no agent
identity and no non-interactive session produces an acceptance in a
later namespace either (`authority.rs` keeps the first-namespace one).

Tests rewritten, and why: `graph/export_tests.rs`
`a_namespace_export_carries_its_own_authority_records_only` asserted
that a `*` genesis reaches every namespace's export — it now asserts
that another namespace's `*` grant and same-id role stay out;
`signing/check_tests.rs` and `verify/order_grid.rs` filed `fixture.ledger`
policies and decisions in a `hafeok.ledger` change-set — they now use one
namespace; `ledger-cli/tests/authority.rs`
`a_grantor_below_star_is_refused_a_grant_over_another_scope` used
`ns:fixture.other` as "another scope" — that grant would now be made in
`fixture.other`'s authority, where the role is not declared, so the test
asserts that refusal and keeps `*` and `set:` for the covering rule.
The pre-policy-binding cases (ruled 2026-10-05) rested on a later
namespace whose genesis was older than its policy, which the writer can
no longer file: `init` dates every namespace's genesis with its policy.
A new helper, `hand::found`, founds a namespace by hand with its genesis
ten seconds older than its first policy, and these tests use it:
`pre_policy_binding.rs` (its module note, `a_signed_binding_before_the_first_policy_is_trusted_and_signs`,
`an_unsigned_binding_before_a_signed_first_policy_fails_l011_and_is_never_trusted`,
`under_a_none_first_policy_d7_alone_decides_a_binding_before_it`) and
`binding_accounting.rs` (`first_policy`). Two tests there change meaning
and are renamed: a skewed add dated before `init`'s binding is now before
the namespace's genesis too, so D7 refuses it
(`a_skewed_add_dated_before_inits_genesis_is_refused_by_d7`); and opening
a namespace over a binding filed in it before any policy no longer turns
the binding into `L011` — it stays a schema fault, now D7's
(`…_and_init_does_not_launder_it`). In `closed_key.rs`
`the_genesis_holders_first_binding_in_a_second_namespace_vouched_by_the_closed_key`
dates the binding after the second namespace's policy: dated before it,
the binding is before that namespace's genesis and D7's, whatever signs
it; the test keeps its point, that the closed key vouches for nothing.
Step 4's `crossing.rs` no longer asserts the absence of `G001`.

The graph stage splits: the authority shapes (`A003`, `A005`) run over
each namespace's graph, the decision shapes (`G001`–`G006`) over the
store's graph — PRD §3.9 says the whole stage runs per namespace, but a
`supersedes` edge in history into another namespace would then be a
permanent `G001`, against ruling 75 (live claims only), and `based_on`
crosses by design. Recorded in LP-8.19 and the close-out.

Protocol: LP-6.5, LP-6.16, LP-6.28, LP-6.31 lose their marks and notes;
LP-5.19 (roles per namespace), LP-8.19 (graph stage per namespace, `A005`
row), LP-8.23, LP-8.31 (notice per namespace, report shape), LP-9.11
(export reach), LP-8.8 (the `ns:<other>` clause); Appendix B row;
Appendix C note "Authority is per namespace". No digest moves; no format
bump; no class added; no grant payload gains a field.

Stacked on #158 (issue 4).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GgRcHXmnAu43hChNxhnqxU
Files the signed bindings the contract-surface gate demands for this
step's 18 exposed Rust surface changes over 8 files: a new declaration
for author/grant_ops.rs with its verdict_knowledge written, and seven
amended — four of them (author/authority_ops.rs, authority/view.rs,
authority/structure.rs, store.rs) with their verdict rewritten where
per-namespace authority had made the old sentence false or silent.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GgRcHXmnAu43hChNxhnqxU
The declaration over ledger-cli/tests/common/hand.rs now says the bound
helper is test-only — the suites' hand-writing harness, no consumer
outside ledger-cli/tests/ — and what hand::found founds.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GgRcHXmnAu43hChNxhnqxU
@Hafeok
Hafeok force-pushed the claude/great-feynman-zcdsdd-step-04-supersedes branch from ec43481 to deb88ef Compare October 11, 2026 06:22
@Hafeok
Hafeok force-pushed the claude/great-feynman-zcdsdd-step-05-authority branch from 73f98fb to 4ee026b Compare October 11, 2026 06:22
Hafeok added a commit that referenced this pull request Oct 11, 2026
…two notices

Issue #149 (PRD issue 6, §3.3; rulings 47 and 69; AC-47 second bullet).
A key is bound, closed and trusted in one namespace. `Authority::of` and
`as_of` now filter key bindings by namespace like every other record;
`key_close::same_key` compares the namespace, so a close ends the key in
its own namespace only and `refusal`'s three rules (another principal's,
closed, already open) look at the binding's namespace; D7 (`filing`)
drops the store-wide branch — the genesis holder's first key in a
namespace is that namespace's self-bound binding, once per namespace,
under its own mandate; the signing check drops `carried_over`, judges a
first policy by a key trusted in its own namespace, and the re-acceptance
deadline runs from the key's earliest close there; `allowed_signers` ends
a line at a close in its own namespace. The writer binds the configured
key self-bound at every `init --namespace` (`carry_genesis_key` and
`no_live_key` go; `--without-key` stays until issue 13), `identity add`'s
self-bound test is per namespace, and signing refuses a key not live in
the namespace (`ToSign.any_namespace` goes). Two repository notices,
never findings (`verify::key_notices`, in the text and JSON report): a
key closed in one namespace and open in another; one key bound to two
principals in two namespaces.

Tests (each failing before the change): `key_across_namespaces.rs`,
rewritten as §3.11 says — bound in A then B, or B then A, a close in A
leaves B open: the key still signs there, A's `allowed_signers` line ends
and B's does not, the split is a notice, the key signs nothing in A, and
a third namespace opens with the same key self-bound; a rotate in A
leaves B's acceptances, review items and `allowed_signers` byte-identical
(AC-47, second bullet); the within-namespace rules of 2026-10-06 stand
(four tests kept); and the carried test, that no agent identity and no
non-interactive session produces a key close. The writer's close in
every namespace held is issue 8's and is not claimed. `genesis_key.rs`:
`init` in a later namespace self-binds the holder's key there under the
namespace's own mandate and signs the first policy with it; with every
key closed elsewhere `init` binds the key again and `verify` notices the
split (JSON `key_split`); with no usable key, `--without-key` initialises
unbound and the unbound-genesis notice names the namespace. The prompt
assigned these rewrites to issue 8; they could not wait, since the
cross-namespace lean they asserted is what this step removes.
`key_close_tests.rs`: three tests now assert the namespace reading.
The pre-policy cases now bind the holder's first key in the second
namespace self-bound: `pre_policy_binding.rs` (`unsigned_before` is
self-bound under `hand::found`'s mandate; the signed case signs the first
policy with the key bound before it, as LP-4.31 now requires; opening a
namespace over a binding filed in it before any policy is refused again,
since the holder's own self-bound binding would no longer be the first
there — `…_and_init_is_refused_over_it`); `binding_accounting.rs`
(`self_bound_by_hand`, one namespace per first binding). `trust.rs`:
`init` self-binds in the second namespace and a further key there is the
holder's own add (renamed from "once per store"); a hand-written
self-bound binding beside `init`'s fails, "first in the namespace".
`genesis_key.rs`'s forged-binding test reads the same wording.
`key_across_namespaces.rs` keeps its four within-namespace tests.

Protocol: LP-4.12, LP-4.13, LP-4.31, LP-4.32, LP-4.37, LP-4.38, LP-4.39
(ruling 101's wording), LP-6.31 (keys note dropped), LP-6.32 (loses its
mark; the writer's close-everywhere noted for issue 8), LP-8.31 (two
notices, report shape); Appendix B row; Appendix C note "Keys are per
namespace". CLAUDE.md updated. No class added; no digest moves (a
binding's hashed fields are unchanged and `namespace` was among them).

Stacked on #159 (issue 5).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GgRcHXmnAu43hChNxhnqxU
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant