Skip to content

feat(aaas): tenant ownership core — epoch-fenced lease store and holder session (A1.1) - #678

Merged
Danceiny merged 1 commit into
mainfrom
feat/aaas-a1-tenant-lease
Oct 31, 2026
Merged

Danceiny merged 1 commit into
mainfrom
feat/aaas-a1-tenant-lease

Conversation

@Danceiny

Copy link
Copy Markdown
Owner

Refs #674

A1 of the AaaS charter must prove a single writer per tenant before any durable copy or router exists. This is its first slice: who may write a tenant, and when they must stop. It lives outside the kernel and is not wired into any runtime path yet. state-ledger.ts and the kernel manifest are untouched, and no dependency is added.

What it adds (ts/src/tenant-state/)

  • Ids. A tenant id is t- plus the first 128 bits of HMAC-SHA256(K_org, issuer ‖ NUL ‖ subject). Organization and tenant validators build every path of the layer from validated ids only (threat S-3). A lone surrogate, a NUL, an empty or oversized claim, and a key under 32 bytes are refused, so two different subjects can never share a tenant.
  • Lease store contract and a reference SQLite store. Linearizable compare-and-set. Acquisition moves the epoch from n to n + 1; renewal and release keep it; a forgotten floor is recorded. Storage triggers refuse an epoch that goes back or skips, a lower floor, and a deleted lease row. Expiry is judged on the store's clock.
  • Holder session (TenantLease). It stops acknowledging at sentAt + ttl − margin on a monotonic clock measured from before the request went out, fails closed when the store is down, resumes only when the epoch never changed, and hands the lease back on a graceful release. A holder never has two renewals or releases outstanding at the store.
  • Conformance checks for any lease store binding. The hotel-be MySQL binding of Q3 must pass them before it holds a real lease, and a deliberately broken store turns them red.

A limit, pinned by a test instead of hidden. The local fence cannot see a frozen clock, such as a process paused between its check and its acknowledgement (test D6). The store refuses that holder on its next renewal, and the epoch-scoped durable copies of A1.2 are what keep its writes from becoming authoritative.

Evidence on 04f938156dc7014df505af0e89fef9661debd383 (local Node v26.9.0)

  • cd ts && npx tsx scripts/tenant-state-tests.ts: exit 0, 28 checks (run-all §89). It includes six real child processes racing one lease file, once fresh and once after a real expiry; exactly one wins each time.
  • Mutation check: I broke 16 guards one at a time. They were the compare-and-set, the expiry boundary, renew-after-expiry, the epoch check, the epoch and delete triggers, the fence deadline and its margin on acquire and on renew, the deadline measured after the round trip, release ordering, terminal loss, call serialization, and the id separator, surrogate and truncation rules. The suite went red every time. The first run let call serialization survive, because a synchronous store cannot show overlap, so a counting async store now covers it.
  • cd ts && npx tsc --noEmit, smoke.ts, g5-guard.ts, kernel-manifest-gate.ts: exit 0, with a zero-diff kernel manifest.
  • Full scripts/run-all-tests.sh on this commit: ALL SUITES GREEN, exit 0. CI runs it again on Node 22 and 24.
  • check-docs-i18n.mjs, check-doc-readability.mjs, run-all-tests-wiring-tests.mjs: OK. The code map has a row in both languages.

Not in this PR. Durable copies, restore and forget are A1.2. The router and handoff are A1.3. Multi-process fault injection and the #275 closure are A1.4. Minimal E2E: N/A, since nothing is wired into a runtime path.

Review ask. Nothing needs a decision. The shape of LeaseStore is the one thing worth a look: it is what the MySQL binding will implement.

…er session (A1.1)

A1 must prove a single writer per tenant before any durable copy or router exists, so the lease protocol and its fencing come first, outside the kernel and not yet wired into a runtime path.

Refs #674
@Danceiny
Danceiny merged commit 86d9406 into main Oct 31, 2026
8 checks passed
@Danceiny
Danceiny deleted the feat/aaas-a1-tenant-lease branch October 31, 2026 06:50
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