plan: claim Plan 9 rev 4 (record_seq in provision record writers with a writer census; A2 fix)
Co-Authored-By: Codex <noreply@openai.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 file changed,
+1786
−171
docs/superpowers/plans/2026-10-08-host-lifecycle-claim-9-activation-readiness.md+1786 −171
CI passed — run details
diff --git a/docs/superpowers/plans/2026-10-08-host-lifecycle-claim-9-activation-readiness.md b/docs/superpowers/plans/2026-10-08-host-lifecycle-claim-9-activation-readiness.md| index 72c6be666..8489438c7 100644 |
| --- a/docs/superpowers/plans/2026-10-08-host-lifecycle-claim-9-activation-readiness.md |
| +++ b/docs/superpowers/plans/2026-10-08-host-lifecycle-claim-9-activation-readiness.md |
| @@ -1,6 +1,8 @@ |
| # Host lifecycle claim, Plan 9: activation readiness |
| -Implementation plan, 2026-10-08, rev 3. Base: `origin/main` at **4f26f15cb41ecfdd9214b49393c228399f34490b**. The remote main ref was checked without fetching or writing git state. Spec: `docs/superpowers/specs/2026-10-02-host-lifecycle-claim-design.md`, rev **6.30**. Scope: amadan discussion **#250**, including its Plan 5, Plan 6b and integration additions. Plans 1 through 8b, 6b, and Plan 5 Tasks 1 through 9 are already on this base; do not replay them. |
| +Implementation plan, 2026-10-08, rev 4. Base: `origin/main` at **4f26f15cb41ecfdd9214b49393c228399f34490b**. The remote main ref was checked without fetching or writing git state. Spec: `docs/superpowers/specs/2026-10-02-host-lifecycle-claim-design.md`, rev **6.30**. Scope: amadan discussion **#250**, including its Plan 5, Plan 6b and integration additions. Plans 1 through 8b, 6b, and Plan 5 Tasks 1 through 9 are already on this base; do not replay them. |
| + |
| +**Rev 4 (2026-10-08).** Moved `record_seq` ownership into provision's record writers. Populated records advance on every write, including raw box transitions without a claim context; first claim writes introduce the counter and never-claimed flag-off records keep main's exact bytes. B8 retains its ABA regression and adds box-writer proofs and a repository-wide record-write census. A2 repairs the self-witnessed fence fixture behind the rev 2 console failure and tests that the invalid evidence still refuses. The six Paul decisions and all of Group D remain unchanged. Base remains the latest remote `origin/main`, `4f26f15`. Verification is recorded at the end. |
| **Rev 3 (2026-10-08).** Applied the B8 ruling: every claim-on record write advances `record_seq`; the resolver proves not-applied from that sequence or its own conditional sequence fence. B8 now includes complete code, passing fence and retry acceptance tests, and the ordinary-rewrite ABA regression. `record_seq` joins C1's release-floor inventory. The six Paul decisions remain open with their text unchanged, and all Group D signatures and executable blocks are unchanged. Base remains the latest remote `origin/main`, `4f26f15`. Verification is recorded at the end. |
| @@ -20,7 +22,7 @@ The task groups retain the discussion's order. Every task states the authority o |
| Use a clean, later implementation checkout based on the pinned main or a reviewed descendant. If main moved, refresh the exact patches and repeat this document's replay checks; do not force a failed hunk. This authoring session performs no git writes. Implementation must later use the repository's reviewed amadan branch workflow, all required gates, and authority review before landing. |
| -Save the following as `$TMPDIR/claim9-apply.py` in the later implementation checkout. It applies one task and phase, without staging or committing. `B4` and `B7` contain only decision-gated tests; do not apply them to a landing branch before Paul's ruling and the follow-on implementation. B8's rev 3 sequence design is decided and its complete acceptance suite must pass. Apply test blocks first, run the task's focused command and record the expected failure, then apply implementation blocks and repeat. Patches use the exact pinned base or the preceding task's result; a failed patch means refresh the plan, not accept fuzz. |
| +Save the following as `$TMPDIR/claim9-apply.py` in the later implementation checkout. It applies one task and phase, without staging or committing. `B4` and `B7` contain only decision-gated tests; do not apply them to a landing branch before Paul's ruling and the follow-on implementation. B8's rev 4 sequence design is decided and its complete acceptance suite must pass. Apply test blocks first, run the task's focused command and record the expected failure, then apply implementation blocks and repeat. Patches use the exact pinned base or the preceding task's result; a failed patch means refresh the plan, not accept fuzz. |
| ```python |
| import pathlib |
| @@ -1456,6 +1458,8 @@ The second check is required because this task creates a new mutation door. Its |
| Tests first. |
| +Rev 4 closes the console failure recorded under rev 3. `writeMoveFence` used the target as its own `written_by` witness. A2's `ValidateTerminationFence` correctly rejects that self-witness with `ErrRoster`, so the door returns 503 before it can treat the target as fenced and return 409. Fix the shared fixture by enrolling a separate active peer in the same account and region, signing that roster, and using the peer as the fence writer. Preserve the expected 409 for valid target/source fences. Add `target-self-fenced` with the original invalid witness: it must return 503 without a claim, registration or move journal. The malformed-state case now reaches its intended state validation with an otherwise valid writer. The full package also caught A2's missing `route POST /api/cli/system/activation/cancel` entry in `testdata/claim-routes.txt`; add it alongside the existing behavioral probe for that route. No production authority check or expected status is weakened, and no A2 test is skipped. Run the complete console package after all decided tasks, not only the move-door filter. |
| + |
| #### New file: `internal/hostactors/plan9_b_test.go` |
| ```go |
| @@ -3049,6 +3053,89 @@ func runSystemCancelActivation(args []string) { |
| Every instance the activator runs used to live in one shared directory as one user, where it could read its neighbours' files. `layout migrate` moves them out: each instance gets its own directory and, from its next wake, its own mount and pid namespace, where it sees its own files and nothing else. An instance that is asleep is moved at once; one that is awake is left for up to an hour to go to sleep, then stopped briefly and moved. `--force` skips the hour. `--box` scopes the request to one box, so a fleet moves one box at a time with `carlos status --layout` showing each box's rows turn `nested`. Once a box's layout is `nested`, a newly provisioned instance on it starts out `nested`; it is never created in the shared directory first. |
| ``` |
| +#### Patch: `internal/console/apiinstancemove_test.go` |
| + |
| +```diff |
| +--- a/internal/console/apiinstancemove_test.go |
| ++++ b/internal/console/apiinstancemove_test.go |
| +@@ -745,19 +745,35 @@ |
| + |
| + // writeMoveFence stores a termination fence for box, as its own regional |
| + // worker would have: observed terminated, for the roster's cloud identity. |
| ++// A box never witnesses its own termination, so the fence is written by a |
| ++// separate active peer in the same region and account, which this adds to |
| ++// the current roster. A body key "written_by" overrides the writer. |
| + func writeMoveFence(t *testing.T, fx *consoleFixture, b hostactors.Box, body map[string]any) { |
| + t.Helper() |
| ++ ctx := context.Background() |
| ++ raw, _, err := fx.rel.Get(ctx, hostactors.Key) |
| ++ if err != nil { |
| ++ t.Fatal(err) |
| ++ } |
| ++ var roster hostactors.Roster |
| ++ if err := release.VerifyRootEnvelope(fx.pub, raw, &roster); err != nil { |
| ++ t.Fatal(err) |
| ++ } |
| ++ writer := moveRosterBox(fleetBox{Box: "fence-writer-" + b.BoxID, Placement: b.Region, AWS: b.Cloud.Region}) |
| ++ writer.Cloud.Account = b.Cloud.Account |
| ++ roster.Boxes[writer.BoxID] = writer |
| ++ putMoveRoster(t, fx, roster) |
| + f := map[string]any{"v": 1, "box": b.BoxID, "enrollment": b.Enrollment, "cloud": b.Cloud, "state": "terminated", |
| + "observed_at": time.Now().UTC(), "via": "describe", "request_id": "request", |
| +- "written_by": map[string]string{"box": b.BoxID, "enrollment": b.Enrollment}} |
| ++ "written_by": map[string]string{"box": writer.BoxID, "enrollment": writer.Enrollment}} |
| + for k, v := range body { |
| + f[k] = v |
| + } |
| +- raw, err := json.Marshal(f) |
| ++ raw, err = json.Marshal(f) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| +- if err := release.CreateExact(context.Background(), fx.rel, "control/box-fences/"+b.Enrollment+".json", raw); err != nil { |
| ++ if err := release.CreateExact(ctx, fx.rel, "control/box-fences/"+b.Enrollment+".json", raw); err != nil { |
| + t.Fatal(err) |
| + } |
| + } |
| +``` |
| + |
| +#### Patch: `internal/console/apiinstancemove_claim_test.go` |
| + |
| +```diff |
| +--- a/internal/console/apiinstancemove_claim_test.go |
| ++++ b/internal/console/apiinstancemove_claim_test.go |
| +@@ -360,6 +360,14 @@ |
| + setup: func(t *testing.T, fx *consoleFixture, _ *claimMoveWorld) { |
| + writeMoveFence(t, fx, moveRosterBox(ukBox("uk-box")), nil) |
| + }}, |
| ++ // A box never witnesses its own termination: a fence the target wrote |
| ++ // about itself is not evidence, and an unusable own fence refuses as |
| ++ // an unreadable roster rather than passing as an absent one. |
| ++ {name: "target-self-fenced", bind: true, admission: true, status: http.StatusServiceUnavailable, msg: "could not read the host roster. try again", |
| ++ setup: func(t *testing.T, fx *consoleFixture, _ *claimMoveWorld) { |
| ++ uk := moveRosterBox(ukBox("uk-box")) |
| ++ writeMoveFence(t, fx, uk, map[string]any{"written_by": map[string]string{"box": uk.BoxID, "enrollment": uk.Enrollment}}) |
| ++ }}, |
| + // The door's roster observation placed both boxes; the roster changed |
| + // before BeginClaimMove's own read (here: the target quarantined while |
| + // the move registered). The refusal does not guess which part of the |
| +``` |
| + |
| +#### Patch: `internal/console/testdata/claim-routes.txt` |
| + |
| +```diff |
| +--- a/internal/console/testdata/claim-routes.txt |
| ++++ b/internal/console/testdata/claim-routes.txt |
| +@@ -215,6 +215,7 @@ |
| + route POST /api/cli/services/{account} |
| + route POST /api/cli/services/{account}/{name}/revoke |
| + route POST /api/cli/services/{account}/{name}/rotate |
| ++route POST /api/cli/system/activation/cancel |
| + route POST /api/cli/system/antivirus |
| + route POST /api/cli/system/layout |
| + route POST /api/cli/system/rollback |
| +``` |
| + |
| ### Task A3: restrict fence object writers (M-2) |
| Invariant: only the exact approved box roles can create `control/box-fences/*`; nobody can replace or delete fence evidence through either the current-object or versioned-object API. |
| @@ -4636,19 +4723,21 @@ func TestPlan9JournalReplacementRemainsUntouched(t *testing.T) { |
| } |
| ``` |
| -### Task B8: resolve an uncertain move start (ResolveMoveStart, rev 3 record_seq ruling) |
| +### Task B8: resolve an uncertain move start (ResolveMoveStart, rev 4 provision-writer ruling) |
| Invariant: only the exact issuing handle may settle its move-start effect, through the effect runner's settlement path. An exact journal entry proves complete. With that entry absent, not-applied requires a current `record_seq` greater than the base sequence of every attempted Put. Neither age, a negative read nor a changed content-hash ETag proves that result. |
| -**Rev 3 ruling and complete implementation.** The rev 2 identical-body and whitespace fences were unsafe: a later ordinary serialization could reproduce the original bytes and ETag, admitting the delayed If-Match Put (ABA). Every claim-on record Put now advances a body counter owned by the conditional record store. A legacy record without `record_seq` has sequence zero; the first claim-on write emits one. Every subsequent write preserves the caller's read sequence and the store advances it by exactly one. Callers cannot remove, lower or choose the counter. A malformed counter or exhausted `uint64` refuses before the raw write. The fence preserves all other fields and conditionally advances the same counter. Its new sequence makes the failed Put's base bytes permanently unavailable to later claim-on rewrites. |
| +**Rev 4 ruling and complete implementation.** The rev 2 identical-body and whitespace fences were unsafe: an ordinary serialization could restore the old bytes and ETag and admit a delayed If-Match Put (ABA). The sequence belongs in provision's record writers. `WriteRecord` preserves and advances a populated `record_seq` regardless of the current flag or context. `WithRecordSequence` is needed only to introduce the field on the first claim-mode write. A never-claimed record without the field retains main's exact serialized bytes. Callers cannot choose, remove or lower the counter; malformed values and exhaustion refuse before Put. `updateRecord` returns the actual sequence written, and a CAS retry rereads the new base. The B8 fence goes through this same provision primitive. |
| **Audit: every move-start attempt is conditional.** `StartClaimMoveRecord` calls `setLifecycleRecordMove`, then `provision.SetRecordMove`/`updateRecord`. Each attempt uses the record ETag it read, including the single retry after a CAS conflict. The mutation boundary and `conditionalRecordStore.putLifecycle` require and forward that nonempty ETag to the raw Put. The recorder associates every read ETag with its exact integer sequence and captures the base of every attempted Put before forwarding it. Resolution uses the highest captured base, not merely the first or last ETag. An unknown or empty If-Match value refuses. The production call path has no unconditional record write; it must retain the mutation boundary and conditional record store. |
| -**Writer and parser inventory.** `ConfigureClaimRuntime` installs the conditional record store only with `HostClaim` enabled and gives that data handle to the console and claim owners. All three provisioning-record arms of `conditionalRecordStore.Put` are covered: lifecycle writes, issued record/control-object effects, and ordinary updates. `PutFrom` buffers record bodies and delegates to that same Put. `putSequencedRecord` checks the current ETag again, validates the caller's preserved sequence, advances it and issues the original conditional Put. Create-only record effects start at one. Non-record keys pass through unchanged. The resolver's narrowly scoped raw fence uses the same sequence helper and the exact current ETag; it grants no generic raw-write or settlement API. |
| +**Writer and parser inventory.** `provision.WriteRecord` is the shared record-key Put primitive. Instance create, sidecar create, `updateRecord` (including every `SetRecordMove` box transition and placement rewrite), and whole-record `UpdateRecord` use it. `conditionalRecordStore.putLifecycle`, ordinary and effect writes, buffered `PutFrom`, both controller-create paths, and the resolver's fence delegate to it. A private context binds an already-prepared Put to the exact key, ETag, original sequence and outgoing bytes, preventing double increments through wrappers. The full console-stack regression runs typed updates and sidecar create through `claimEffectStore`, the observed mutation boundary and the conditional record store, checking stored and returned counters. The mutation observer and effect input hash describe the submitted bytes; the provision encoding step may add the first counter below them. Those hashes are not record ETags or settlement evidence. It carries no authority: the console app gate and lifecycle checks still run, and boxes keep their claim-op/guarded-transition authority. If the inner claim boundary introduces the first counter, the typed outer writer receives its actual value. Lifecycle comparisons exclude only the provision-owned counter, whose exact base and advancement are checked again by the helper before the raw Put. |
| + |
| +`provision.Record` and `SidecarRecord` retain the additive `RecordSeq uint64` field with `omitempty`. `RecordSequence` parses integers without float conversion and refuses duplicate, aliased, null, fractional, negative and overflowing values. Counter advancement preserves unknown JSON fields. Existing typed parsers retain the counter; holder identity and incarnation do not include it. Group C/C1 includes the field in the reader/writer floor. |
| -`provision.Record` and `provision.SidecarRecord` retain the additive `RecordSeq uint64` field with `omitempty`. This covers instance `DecodeRecord`/Get/list/reconcile, sidecar Get/list/reconcile, hostops `ReadRecord`, ordinary typed updates and lifecycle serialization. Claim holder identity and incarnation do not include the counter. The sequence helper retains unknown JSON fields and parses integers without a float conversion; duplicate, aliased, null, fractional, negative and overflowing counter encodings refuse. Lifecycle callers receive the counter actually written. `Group C/C1` includes `record_seq` in the reader and writer floor inventory. Record deletion is not a sequence reset operation: the resolver refuses an absent or replaced incarnation, and existing retirement/recreation authority still requires a new incarnation before creating another record. An unresolved move registration continues to block retirement and recreation. |
| +**Box writers, copies and retirement.** `movesPass` still captures the claim flag to introduce the first sequence on a legacy record. Once present, raw `SetRecordMove`/`updateRecord` advances it without that context: cut, flip, revert, landing, rollback, and future commit/open/close callbacks all share this writer. Reconcile on this base reads record objects and writes local registry rows; it does not Put a record. Typed retirement deletes the record under its existing incarnation and claim authority; it does not rewrite or reset it. Any future reconcile or retirement record Put must use the provision writer and be reviewed in the census diff. Account migration's generic object-copy loop is another record-key path: its record arm now uses `WriteRecord`, preserving legacy create-only bytes and refusing a populated source counter at a new key. A preflight checks the whole copy plan before any copy, including `--dry-run`; each record copy repeats that check against a changed source. Malformed record bodies also refuse at this boundary rather than being copied as opaque bytes. This is an intentional refusal for previously claim-written records, with a message identifying the required new-incarnation protocol. Restoring an old record body is not a new-incarnation protocol. An absent or replaced record remains uncertain to B8; an unresolved move registration still blocks retirement/recreation. |
| -**Box writers and versioned writes.** The box moves pass captures `hostwrite.Config.Enabled` independently of the moves switch. With the claim flag on, it sets `provision.WithRecordSequence` on the pass context; all ordinary, phase-1, cut and landing record updates inherit it. `updateRecord` preserves the callback's base counter, increments before each conditional Put and returns that actual value; a CAS retry rereads and advances the new base. Console writes do not set this context because their conditional store owns advancement. `protectedFieldsEqual` prevents typed callbacks and whole-record updates from altering the counter. These changes add no Group D signature. |
| +**Census.** `TestPlan9B8RecordWriteCensus` scans all production Go source across the repository, including command code and build-tagged files. Its explicit reviewed inventory covers Put/PutFrom/PutIfAbsent/CopyVersion selectors and method values plus record-key constructors and path literals. Added, removed or changed sites fail until their function body and record-key routing are audited. This is deliberately broader than a search for `RecordKey` on the Put line: variable keys, generic copy loops and transports are included. Negative fixtures exercise a new raw writer, a sidecar key, a box writer and a bound Put method. The inventory distinguishes record writers, guarded transports and unrelated objects; the runtime tests prove advancement and refusal at the shared path. Run the census on the fully replayed decided plan, including Group D; do not regenerate its reviewed inventory merely to make a failure green. |
| The claim-on `versionedBoundary` refuses record `CopyVersion` destinations and `PutIfAbsent`, and refuses generic record `DeleteVersion`: any of these could restore older bytes without advancing the counter. Source reads retain their existing authorization. Only `DeletePurgeSweep` can supply the private retired-record deletion scope, under its existing exclusive purge authority. It rechecks current-record absence before every deletion and orders record data versions before delete markers, including replay of older journals. Both the sweep and boundary refuse a marker deletion while any data version remains, so a sweep cannot temporarily resurrect an old sequence. Tests exercise versioned calls with a real issued app permit, late live records, and an interleaved data/marker history whose record must stay absent throughout deletion. |
| @@ -4658,14 +4747,15 @@ The claim-on `versionedBoundary` refuses record `CopyVersion` destinations and ` |
| **Moves integration and rollout.** The hostops acceptance tests reproduce moves-phase1-2 `26d9e4d1`'s `TestMoveDoorHostClaimRetryAfterAFailedRecordStart`: resolve before releasing the claim, use the exact journal on complete, and compensate and drain only on proved not-applied. Then a new registration/move ID can retry. Complete reports application of the record-start effect; the move's success, abort or rollback remains in its journal. On uncertain, retain the registration and claim and promise no retry. All `TestPlan9B8Acceptance*` cases and `TestPlan9B8RefusesUnprovenContentFence` must now pass; the ABA case performs a real ordinary `SetRecordRegion` through the claim-on record store after grant-bound release, restores the original business fields, and proves the delayed Put still conflicts. Wiring the moves door and removing its skip remains that session's integration step; this task supplies its completed resolver contract. |
| -Deploy the sequence-aware readers and every claim-on writer before enabling the resolver or claim mode, under Group C's existing approvals. Do not mix older writers that can strip the field into that rollout. Flag-off records at legacy zero retain main's exact bytes and emit no `record_seq`; the sequence store is never installed on that path. Populated records must remain readable without losing their counter. The six Paul decisions remain open and unchanged. No Group D interface or signature changes. |
| +Deploy the sequence-aware readers and every claim-on writer before enabling the resolver or claim mode, under Group C's existing approvals. Do not mix older writers that can strip the field into that rollout. Flag-off records at legacy zero retain main's exact bytes and emit no `record_seq`; the sequence store is never installed on that path. Populated records must remain readable and every subsequent writer must advance their counter, even if its current claim flag is off. The six Paul decisions remain open and unchanged. No Group D interface or signature changes. |
| Tests first: apply the new test files before the implementation and record the missing API/field compile failure. Rev 2's four fence-dependent acceptance tests also fail against its partial resolver. After applying the complete blocks, run these commands serially. There are no excluded B8 acceptance cases. |
| ```bash |
| nice -n 10 go test -p 1 ./internal/hostops ./internal/provision ./internal/host -run '^TestPlan9B8' -count=1 |
| nice -n 10 go test -p 1 ./internal/hostops ./internal/provision -count=1 |
| -nice -n 10 go test -p 1 ./internal/console -run 'ClaimAuthority|MoveDoorHostClaimFailedRecordStart' -count=1 |
| +TMPDIR=/tmp/g9 nice -n 10 go test -p 1 ./internal/console -count=1 |
| +nice -n 10 go test -p 1 . -run 'Plan9B8|AccountsMigrate|Migration' -count=1 |
| nice -n 10 go test -p 1 ./internal/host -run 'MovesPass|Phase1|Cut|Landing' -count=1 |
| nice -n 10 go vet ./internal/hostops ./internal/provision ./internal/host |
| ``` |
| @@ -5188,6 +5278,41 @@ func TestPlan9B8AcceptanceOrdinaryRewriteCannotReopenFenceABA(t *testing.T) { |
| t.Fatalf("delayed Put survived fence and ordinary rewrite: %v", err) |
| } |
| } |
| + |
| +func TestPlan9B8AcceptanceBoxRewriteFencesWithoutClaimContext(t *testing.T) { |
| + ctx := context.Background() |
| + r := newResolveFixture(t, false) |
| + // First fence introduces the sequence; the next box write must advance it |
| + // even without WithRecordSequence or a console conditional-record wrapper. |
| + if got, err := ResolveMoveStart(ctx, r.h, r.moving, moveStartID); err != nil || got != OutcomeNotApplied { |
| + t.Fatalf("resolve: %q %v", got, err) |
| + } |
| + if _, err := provision.SetRecordMove(ctx, r.raw.Store, "bab", "app", r.host, func(*provision.Record) error { return nil }); err != nil { |
| + t.Fatal(err) |
| + } |
| + body, _ := r.record(t) |
| + _, seq, err := recordSequence(body) |
| + if err != nil || seq != 2 { |
| + t.Fatalf("box seq=%d %v", seq, err) |
| + } |
| + if err := r.latePut(); !errors.Is(err, release.ErrCASConflict) { |
| + t.Fatalf("box reopened fence: %v", err) |
| + } |
| +} |
| + |
| +func TestPlan9B8AcceptanceBoxAdvanceProvesNotApplied(t *testing.T) { |
| + ctx := context.Background() |
| + r := newResolveFixture(t, false) |
| + if _, err := provision.SetRecordMove(provision.WithRecordSequence(ctx), r.raw.Store, "bab", "app", r.host, func(*provision.Record) error { return nil }); err != nil { |
| + t.Fatal(err) |
| + } |
| + if got, err := ResolveMoveStart(ctx, r.h, r.moving, moveStartID); err != nil || got != OutcomeNotApplied || r.control.fences != 0 { |
| + t.Fatalf("box proof: %q %v fences=%d", got, err, r.control.fences) |
| + } |
| + if err := r.latePut(); !errors.Is(err, release.ErrCASConflict) { |
| + t.Fatalf("late Put: %v", err) |
| + } |
| +} |
| ``` |
| #### New file: `internal/hostops/plan9_b8_sequence_test.go` |
| @@ -5692,7 +5817,9 @@ func TestPlan9B8PurgeRemovesRecordMarkersLast(t *testing.T) { |
| package provision |
| import ( |
| + "bytes" |
| "context" |
| + "encoding/json" |
| "errors" |
| "testing" |
| @@ -5738,6 +5865,170 @@ func TestPlan9B8BoxRecordSequenceAndLegacyBytes(t *testing.T) { |
| t.Fatalf("whole record removed sequence: %v", err) |
| } |
| } |
| + |
| +func TestPlan9B8PopulatedSequenceAdvancesWithoutClaimContext(t *testing.T) { |
| + s := release.NewDirStore(t.TempDir()) |
| + ctx := context.Background() |
| + rec := acmeRecord("bdm") |
| + if err := CreateRecord(WithRecordSequence(ctx), s, rec); err != nil { |
| + t.Fatal(err) |
| + } |
| + // Every box transition, including future commit/open/close callbacks, uses |
| + // SetRecordMove on the raw store. A populated record needs no flag context. |
| + for want := uint64(2); want <= 5; want++ { |
| + got, err := SetRecordMove(ctx, s, rec.Account, rec.App, rec.Host, func(*Record) error { return nil }) |
| + if err != nil || got.RecordSeq != want { |
| + t.Fatalf("box: %+v %v", got, err) |
| + } |
| + } |
| + got, tag, err := GetRecordETag(ctx, s, rec.Account, rec.App, rec.Host) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + got.Channel = "new-channel" |
| + if err := UpdateRecord(ctx, s, *got, tag); err != nil { |
| + t.Fatal(err) |
| + } |
| + got, err = GetRecord(ctx, s, rec.Account, rec.App, rec.Host) |
| + if err != nil || got.RecordSeq != 6 { |
| + t.Fatalf("whole record: %+v %v", got, err) |
| + } |
| + if err := SetRecordRegion(ctx, s, rec.Account, rec.App, rec.Host, rec.Region); err != nil { |
| + t.Fatal(err) |
| + } |
| + got, err = GetRecord(ctx, s, rec.Account, rec.App, rec.Host) |
| + if err != nil || got.RecordSeq != 7 { |
| + t.Fatalf("placement: %+v %v", got, err) |
| + } |
| +} |
| + |
| +func TestPlan9B8CreateBytesAndSidecarSequence(t *testing.T) { |
| + for _, claim := range []bool{false, true} { |
| + ctx := context.Background() |
| + if claim { |
| + ctx = WithRecordSequence(ctx) |
| + } |
| + s := release.NewDirStore(t.TempDir()) |
| + rec := acmeRecord("bdm") |
| + side := SidecarRecord{Schema: 1, Account: rec.Account, App: rec.App, Name: "worker", Host: SidecarHost("worker", rec.App, rec.Account), CreatedAt: rec.CreatedAt} |
| + if err := CreateRecord(ctx, s, rec); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := CreateSidecarRecord(ctx, s, side); err != nil { |
| + t.Fatal(err) |
| + } |
| + for _, tc := range []struct { |
| + key string |
| + value any |
| + }{{RecordKey(rec.Account, rec.App, rec.Host), rec}, {SidecarRecordKey(side.Account, side.App, side.Name), side}} { |
| + raw, _, err := s.Get(ctx, tc.key) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + _, seq, err := RecordSequence(raw) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + want := uint64(0) |
| + if claim { |
| + want = 1 |
| + } |
| + if seq != want { |
| + t.Fatalf("%s sequence=%d", tc.key, seq) |
| + } |
| + if !claim { |
| + expected, err := json.Marshal(tc.value) |
| + if err != nil || !bytes.Equal(raw, expected) || bytes.Contains(raw, []byte("record_seq")) { |
| + t.Fatalf("legacy bytes %s: %s", tc.key, raw) |
| + } |
| + } |
| + } |
| + } |
| +} |
| + |
| +type plan9SequenceRetry struct { |
| + release.Store |
| + key string |
| + raced bool |
| +} |
| + |
| +func (s *plan9SequenceRetry) Put(ctx context.Context, key string, body []byte, tag string) (string, error) { |
| + if key == s.key && !s.raced { |
| + s.raced = true |
| + // A different box wins the first CAS. Its write advances the same counter. |
| + raw, current, err := s.Store.Get(ctx, key) |
| + if err != nil { |
| + return "", err |
| + } |
| + if _, _, err = WriteRecord(context.Background(), s.Store, key, raw, raw, current); err != nil { |
| + return "", err |
| + } |
| + } |
| + return s.Store.Put(ctx, key, body, tag) |
| +} |
| + |
| +func TestPlan9B8BoxCASRetryRereadsSequence(t *testing.T) { |
| + ctx := context.Background() |
| + base := release.NewDirStore(t.TempDir()) |
| + rec := acmeRecord("bdm") |
| + if err := CreateRecord(WithRecordSequence(ctx), base, rec); err != nil { |
| + t.Fatal(err) |
| + } |
| + s := &plan9SequenceRetry{Store: base, key: RecordKey(rec.Account, rec.App, rec.Host)} |
| + got, err := SetRecordMove(ctx, s, rec.Account, rec.App, rec.Host, func(*Record) error { return nil }) |
| + if err != nil || !s.raced || got.RecordSeq != 3 { |
| + t.Fatalf("retry: %+v %v", got, err) |
| + } |
| +} |
| + |
| +func TestPlan9B8WholeRecordUsesRawSequenceBase(t *testing.T) { |
| + for _, field := range []string{`"record_seq":0`, `"Record_Seq":0`, `"record_seq":0,"record_seq":0`, `"record_seq":null`} { |
| + t.Run(field, func(t *testing.T) { |
| + ctx := context.Background() |
| + s := release.NewDirStore(t.TempDir()) |
| + r := acmeRecord("bdm") |
| + body, _ := json.Marshal(r) |
| + body = append(append([]byte(nil), body[:len(body)-1]...), []byte(","+field+"}")...) |
| + key := RecordKey(r.Account, r.App, r.Host) |
| + tag, err := s.Put(ctx, key, body, "") |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + err = UpdateRecord(ctx, s, r, tag) |
| + if field == `"record_seq":0` { |
| + got, getErr := GetRecord(ctx, s, r.Account, r.App, r.Host) |
| + if err != nil || getErr != nil || got.RecordSeq != 1 { |
| + t.Fatalf("present zero: %+v %v %v", got, err, getErr) |
| + } |
| + } else { |
| + if !errors.Is(err, ErrRecordSequence) { |
| + t.Fatalf("malformed base: %v", err) |
| + } |
| + got, _, getErr := s.Get(ctx, key) |
| + if getErr != nil || !bytes.Equal(got, body) { |
| + t.Fatalf("refusal wrote: %s %v", got, getErr) |
| + } |
| + } |
| + }) |
| + } |
| +} |
| + |
| +func TestPlan9B8BoxPopulatedCounterNeedsNoContext(t *testing.T) { |
| + ctx := context.Background() |
| + store := release.NewDirStore(t.TempDir()) |
| + r := acmeRecord("bdm") |
| + if err := CreateRecord(ctx, store, r); err != nil { |
| + t.Fatal(err) |
| + } |
| + first, err := SetRecordMove(WithRecordSequence(ctx), store, r.Account, r.App, r.Host, func(*Record) error { return nil }) |
| + if err != nil || first.RecordSeq != 1 { |
| + t.Fatalf("seed: %+v %v", first, err) |
| + } |
| + got, err := SetRecordMove(ctx, store, r.Account, r.App, r.Host, func(*Record) error { return nil }) |
| + if err != nil || got.RecordSeq != 2 { |
| + t.Fatalf("unflagged populated write: %+v %v", got, err) |
| + } |
| +} |
| ``` |
| #### New file: `internal/host/plan9_b8_sequence_test.go` |
| @@ -5780,89 +6071,37 @@ func TestPlan9B8MovesPassUsesCapturedClaimFlag(t *testing.T) { |
| package hostops |
| import ( |
| - "bytes" |
| "context" |
| "encoding/json" |
| "errors" |
| - "io" |
| "os" |
| - "strconv" |
| - "strings" |
| + "github.com/carlosframework/platform/internal/provision" |
| "github.com/carlosframework/platform/internal/release" |
| ) |
| -// recordSequence preserves unknown fields and parses the counter without a |
| -// float64 round trip. Missing means legacy zero; null, aliases, duplicates, |
| -// fractions, negatives and overflow refuse at the claim-on write boundary. |
| -func recordSequence(body []byte) (map[string]json.RawMessage, uint64, error) { |
| - d := json.NewDecoder(bytes.NewReader(body)) |
| - if first, err := d.Token(); err != nil || first != json.Delim('{') { |
| - return nil, 0, ErrIdentity |
| - } |
| - fields := map[string]json.RawMessage{} |
| - var seq uint64 |
| - for d.More() { |
| - token, err := d.Token() |
| - if err != nil { |
| - return nil, 0, ErrIdentity |
| - } |
| - name, ok := token.(string) |
| - if !ok { |
| - return nil, 0, ErrIdentity |
| - } |
| - if _, exists := fields[name]; exists { |
| - return nil, 0, ErrIdentity |
| - } |
| - var value json.RawMessage |
| - if d.Decode(&value) != nil { |
| - return nil, 0, ErrIdentity |
| - } |
| - if strings.EqualFold(name, "record_seq") { |
| - if name != "record_seq" { |
| - return nil, 0, ErrIdentity |
| - } |
| - seq, err = strconv.ParseUint(string(value), 10, 64) |
| - if err != nil { |
| - return nil, 0, ErrIdentity |
| - } |
| - } |
| - fields[name] = value |
| - } |
| - if end, err := d.Token(); err != nil || end != json.Delim('}') { |
| - return nil, 0, ErrIdentity |
| +func sequenceError(err error) error { |
| + if errors.Is(err, provision.ErrRecordSequence) { |
| + return ErrIdentity |
| } |
| - if _, err := d.Token(); err != io.EOF { |
| - return nil, 0, ErrIdentity |
| + if errors.Is(err, provision.ErrRecordSequenceExhausted) { |
| + return ErrPending |
| } |
| - return fields, seq, nil |
| + return err |
| +} |
| + |
| +func recordSequence(body []byte) (map[string]json.RawMessage, uint64, error) { |
| + fields, seq, err := provision.RecordSequence(body) |
| + return fields, seq, sequenceError(err) |
| } |
| -// advanceRecordSequence alone owns the counter. Callers must preserve the |
| -// read value, including on retries; they cannot reset it or choose a future |
| -// revision. Exhaustion refuses instead of wrapping to a reusable body. |
| func advanceRecordSequence(before, body []byte) ([]byte, error) { |
| - _, base, err := recordSequence(before) |
| - if err != nil { |
| - return nil, err |
| - } |
| - fields, candidate, err := recordSequence(body) |
| - if err != nil { |
| - return nil, err |
| - } |
| - if candidate != base { |
| - return nil, ErrIdentity |
| - } |
| - if base == ^uint64(0) { |
| - return nil, ErrPending |
| - } |
| - fields["record_seq"] = json.RawMessage(strconv.FormatUint(base+1, 10)) |
| - return json.Marshal(fields) |
| + next, err := provision.AdvanceRecordSequence(before, body) |
| + return next, sequenceError(err) |
| } |
| -// putSequencedRecord is shared by every conditional-record Put arm, including |
| -// issued record/control-object effects and PutFrom's buffered path. The |
| -// constructor is claim-on only. Non-record and flag-off writes never use it. |
| +// This claim-only boundary delegates encoding to provision, including when |
| +// an upstream typed writer has already prepared the same conditional Put. |
| func putSequencedRecord(ctx context.Context, raw release.Store, key string, body []byte, etag string) (string, error) { |
| before, seen, err := raw.Get(ctx, key) |
| if errors.Is(err, os.ErrNotExist) { |
| @@ -5876,12 +6115,9 @@ func putSequencedRecord(ctx context.Context, raw release.Store, key string, body |
| if seen != etag { |
| return "", release.ErrCASConflict |
| } |
| - next, err := advanceRecordSequence(before, body) |
| - if err != nil { |
| - return "", err |
| - } |
| MarkAppMutationIssued(ctx) |
| - return raw.Put(ctx, key, next, etag) |
| + tag, _, err := provision.WriteRecord(provision.WithRecordSequence(ctx), raw, key, before, body, etag) |
| + return tag, sequenceError(err) |
| } |
| ``` |
| @@ -5937,76 +6173,215 @@ func retiredRecordVersion(ctx context.Context, control release.Store, versions l |
| ```go |
| package provision |
| -import "context" |
| - |
| -type recordSequenceKey struct{} |
| - |
| -// WithRecordSequence selects sequence advancement for box-side move writers |
| -// using the raw conditional store. The host sets it from its captured claim |
| -// flag. Console writers instead advance at their conditional-record boundary; |
| -// they must not set this context. This supplies no mutation authority. |
| -func WithRecordSequence(ctx context.Context) context.Context { |
| - return context.WithValue(ctx, recordSequenceKey{}, true) |
| -} |
| -``` |
| - |
| -#### New file: `internal/hostops/move_start_resolve.go` |
| - |
| -```go |
| -package hostops |
| - |
| import ( |
| + "bytes" |
| "context" |
| + "encoding/json" |
| "errors" |
| - "sync" |
| - "sync/atomic" |
| + "io" |
| + "strconv" |
| + "strings" |
| - "github.com/carlosframework/platform/internal/hostclaim" |
| - "github.com/carlosframework/platform/internal/provision" |
| "github.com/carlosframework/platform/internal/release" |
| ) |
| -// moveStartAction is the action of StartClaimMoveRecord's record effect, |
| -// the only effect kind ResolveMoveStart settles. |
| -const moveStartAction = "move-start" |
| - |
| -// Outcome is ResolveMoveStart's answer for one move-start effect. |
| -type Outcome string |
| +// IsRecordKey recognizes both instance and sidecar control records. |
| +func IsRecordKey(key string) bool { |
| + p := strings.Split(key, "/") |
| + return len(p) == 5 && p[0] == "apps" && (p[3] == "instances" || p[3] == "sidecars") && strings.HasSuffix(p[4], ".json") |
| +} |
| -const ( |
| - // OutcomeComplete: the record-start effect applied. The journal retains |
| - // the move's own outcome, including a later abort or rollback. |
| - OutcomeComplete Outcome = "complete" |
| - // OutcomeNotApplied: it does not, and no record Put the start issued |
| - // can still land. |
| - OutcomeNotApplied Outcome = "not-applied" |
| - // OutcomeUncertain: neither is established; the effect stays uncertain |
| - // and the error says why. |
| - OutcomeUncertain Outcome = "uncertain" |
| -) |
| +var ErrRecordSequence = errors.New("provision: invalid record sequence") |
| +var ErrRecordSequenceExhausted = errors.New("provision: record sequence exhausted") |
| -// ErrMoveStartFenceUnproven retains uncertainty when capture is missing, |
| -// the sequence regressed or repeated CAS conflicts prevented a proof. |
| -var ErrMoveStartFenceUnproven = errors.New("hostops: move-start fence is unproven") |
| +type recordSequenceKey struct{} |
| +type preparedRecordKey struct{} |
| -// moveStartPuts binds every attempted conditional Put to its read sequence. |
| -// Only the issuing handle holds it. Missing capture cannot prove a fence. |
| -type moveStartPuts struct { |
| - id, incarnation, key string |
| - holder hostclaim.Holder |
| - mu sync.Mutex |
| - reads map[string]uint64 |
| - bases []uint64 |
| +// WithRecordSequence introduces the counter on a first claim-mode write. |
| +// A populated counter advances even without this context. This is encoding, |
| +// not mutation authority; callers retain their existing claim and app gates. |
| +func WithRecordSequence(ctx context.Context) context.Context { |
| + return context.WithValue(ctx, recordSequenceKey{}, true) |
| } |
| -func (p *moveStartPuts) highestBase() (uint64, bool) { |
| - p.mu.Lock() |
| - defer p.mu.Unlock() |
| - var high uint64 |
| - for _, seq := range p.bases { |
| - if seq > high { |
| - high = seq |
| - } |
| +// RecordSequence preserves unknown fields and parses the counter without a |
| +// float64 round trip. Missing means legacy zero; null, aliases, duplicates, |
| +// fractions, negatives and overflow refuse at the claim-on write boundary. |
| +func RecordSequence(body []byte) (map[string]json.RawMessage, uint64, error) { |
| + d := json.NewDecoder(bytes.NewReader(body)) |
| + if first, err := d.Token(); err != nil || first != json.Delim('{') { |
| + return nil, 0, ErrRecordSequence |
| + } |
| + fields := map[string]json.RawMessage{} |
| + var seq uint64 |
| + for d.More() { |
| + token, err := d.Token() |
| + if err != nil { |
| + return nil, 0, ErrRecordSequence |
| + } |
| + name, ok := token.(string) |
| + if !ok { |
| + return nil, 0, ErrRecordSequence |
| + } |
| + if _, exists := fields[name]; exists { |
| + return nil, 0, ErrRecordSequence |
| + } |
| + var value json.RawMessage |
| + if d.Decode(&value) != nil { |
| + return nil, 0, ErrRecordSequence |
| + } |
| + if strings.EqualFold(name, "record_seq") { |
| + if name != "record_seq" { |
| + return nil, 0, ErrRecordSequence |
| + } |
| + seq, err = strconv.ParseUint(string(value), 10, 64) |
| + if err != nil { |
| + return nil, 0, ErrRecordSequence |
| + } |
| + } |
| + fields[name] = value |
| + } |
| + if end, err := d.Token(); err != nil || end != json.Delim('}') { |
| + return nil, 0, ErrRecordSequence |
| + } |
| + if _, err := d.Token(); err != io.EOF { |
| + return nil, 0, ErrRecordSequence |
| + } |
| + return fields, seq, nil |
| +} |
| + |
| +// AdvanceRecordSequence alone owns the counter. Callers must preserve the |
| +// read value, including on retries; they cannot reset it or choose a future |
| +// revision. Exhaustion refuses instead of wrapping to a reusable body. |
| +func AdvanceRecordSequence(before, body []byte) ([]byte, error) { |
| + _, base, err := RecordSequence(before) |
| + if err != nil { |
| + return nil, err |
| + } |
| + fields, candidate, err := RecordSequence(body) |
| + if err != nil { |
| + return nil, err |
| + } |
| + if candidate != base { |
| + return nil, ErrRecordSequence |
| + } |
| + if base == ^uint64(0) { |
| + return nil, ErrRecordSequenceExhausted |
| + } |
| + fields["record_seq"] = json.RawMessage(strconv.FormatUint(base+1, 10)) |
| + return json.Marshal(fields) |
| +} |
| + |
| +// preparedRecord binds one synchronous Put to its bytes and CAS base. Store |
| +// wrappers may call WriteRecord again without advancing twice. An inner |
| +// claim boundary can introduce the counter and report the written value back |
| +// to the typed outer writer. This private context grants no write authority. |
| +type preparedRecord struct { |
| + key, etag string |
| + body []byte |
| + base, seq uint64 |
| + sequenced bool |
| +} |
| + |
| +// WriteRecord is the only record-key Put primitive. before is the exact |
| +// If-Match read ({} for create). body preserves that read's counter. Never |
| +// call it with a synthesized before to reset a live record's sequence. |
| +// Legacy absent counters and no claim context retain the exact body bytes. |
| +func WriteRecord(ctx context.Context, s release.Store, key string, before, body []byte, etag string) (string, uint64, error) { |
| + if !IsRecordKey(key) { |
| + return "", 0, ErrRecordSequence |
| + } |
| + fields, base, err := RecordSequence(before) |
| + if err != nil { |
| + return "", 0, err |
| + } |
| + _, candidate, err := RecordSequence(body) |
| + if err != nil { |
| + return "", 0, err |
| + } |
| + enabled, _ := ctx.Value(recordSequenceKey{}).(bool) |
| + _, present := fields["record_seq"] |
| + p, nested := ctx.Value(preparedRecordKey{}).(*preparedRecord) |
| + if nested { |
| + if p.key != key || p.etag != etag || p.base != base || p.seq != candidate || !bytes.Equal(p.body, body) { |
| + return "", 0, ErrRecordSequence |
| + } |
| + } else { |
| + if candidate != base { |
| + return "", 0, ErrRecordSequence |
| + } |
| + p = &preparedRecord{key: key, etag: etag, body: append([]byte(nil), body...), base: base, seq: base} |
| + ctx = context.WithValue(ctx, preparedRecordKey{}, p) |
| + } |
| + if !p.sequenced && (enabled || present) { |
| + body, err = AdvanceRecordSequence(before, body) |
| + if err != nil { |
| + return "", 0, err |
| + } |
| + p.body, p.seq, p.sequenced = append([]byte(nil), body...), base+1, true |
| + } |
| + tag, err := s.Put(ctx, key, body, etag) |
| + return tag, p.seq, err |
| +} |
| +``` |
| + |
| +#### New file: `internal/hostops/move_start_resolve.go` |
| + |
| +```go |
| +package hostops |
| + |
| +import ( |
| + "context" |
| + "errors" |
| + "sync" |
| + "sync/atomic" |
| + |
| + "github.com/carlosframework/platform/internal/hostclaim" |
| + "github.com/carlosframework/platform/internal/provision" |
| + "github.com/carlosframework/platform/internal/release" |
| +) |
| + |
| +// moveStartAction is the action of StartClaimMoveRecord's record effect, |
| +// the only effect kind ResolveMoveStart settles. |
| +const moveStartAction = "move-start" |
| + |
| +// Outcome is ResolveMoveStart's answer for one move-start effect. |
| +type Outcome string |
| + |
| +const ( |
| + // OutcomeComplete: the record-start effect applied. The journal retains |
| + // the move's own outcome, including a later abort or rollback. |
| + OutcomeComplete Outcome = "complete" |
| + // OutcomeNotApplied: it does not, and no record Put the start issued |
| + // can still land. |
| + OutcomeNotApplied Outcome = "not-applied" |
| + // OutcomeUncertain: neither is established; the effect stays uncertain |
| + // and the error says why. |
| + OutcomeUncertain Outcome = "uncertain" |
| +) |
| + |
| +// ErrMoveStartFenceUnproven retains uncertainty when capture is missing, |
| +// the sequence regressed or repeated CAS conflicts prevented a proof. |
| +var ErrMoveStartFenceUnproven = errors.New("hostops: move-start fence is unproven") |
| + |
| +// moveStartPuts binds every attempted conditional Put to its read sequence. |
| +// Only the issuing handle holds it. Missing capture cannot prove a fence. |
| +type moveStartPuts struct { |
| + id, incarnation, key string |
| + holder hostclaim.Holder |
| + mu sync.Mutex |
| + reads map[string]uint64 |
| + bases []uint64 |
| +} |
| + |
| +func (p *moveStartPuts) highestBase() (uint64, bool) { |
| + p.mu.Lock() |
| + defer p.mu.Unlock() |
| + var high uint64 |
| + for _, seq := range p.bases { |
| + if seq > high { |
| + high = seq |
| + } |
| } |
| return high, len(p.bases) > 0 |
| } |
| @@ -6191,11 +6566,7 @@ func decideMoveStart(ctx context.Context, w *ControlWriter, puts *moveStartPuts, |
| if fresh.Absent || fresh.ETag != o.ETag { |
| return "", hostclaim.ErrConflict |
| } |
| - body, err := advanceRecordSequence(current.Body, current.Body) |
| - if err != nil { |
| - return "", err |
| - } |
| - if _, err = w.raw.Put(ctx, puts.key, body, current.ETag); err == nil { |
| + if _, _, err = provision.WriteRecord(provision.WithRecordSequence(ctx), w.raw, puts.key, current.Body, current.Body, current.ETag); err == nil { |
| return "not-applied", nil |
| } else if !errors.Is(err, release.ErrCASConflict) { |
| return "", err |
| @@ -6215,24 +6586,62 @@ func decideMoveStart(ctx context.Context, w *ControlWriter, puts *moveStartPuts, |
| // Account vs the key's segments); GetRecord and ListRecords enforce that, a |
| // record that lies about identity is never guessed at (design §3.2). |
| type Record struct { |
| -+ // RecordSeq is advanced by the claim-on record store; absent is legacy zero. |
| ++ // RecordSeq is advanced by every record writer once present; absent is legacy zero. |
| + RecordSeq uint64 `json:"record_seq,omitempty"` |
| Acquisition string `json:"acq,omitempty"` |
| // Maintenance is preserved by ordinary writers; only a future guarded |
| // maintenance transition may install, replace, or clear it. |
| -@@ -909,6 +911,12 @@ |
| - if !protectedFieldsEqual(before, rec) { |
| - return nil, ErrMaintenanceProjection |
| - } |
| -+ if enabled, _ := ctx.Value(recordSequenceKey{}).(bool); enabled { |
| -+ if rec.RecordSeq == ^uint64(0) { |
| -+ return nil, ErrMaintenanceProjection |
| -+ } |
| -+ rec.RecordSeq++ |
| -+ } |
| - body, err := json.Marshal(rec) |
| +@@ -850,7 +852,7 @@ |
| + if err != nil { |
| + return err |
| + } |
| +- _, err = s.Put(ctx, RecordKey(rec.Account, rec.App, rec.Host), body, "") |
| ++ _, _, err = WriteRecord(ctx, s, RecordKey(rec.Account, rec.App, rec.Host), []byte(`{}`), body, "") |
| + return err |
| + } |
| + |
| +@@ -913,9 +915,11 @@ |
| if err != nil { |
| return nil, err |
| + } |
| +- if _, err := s.Put(ctx, key, body, etag); err != nil { |
| ++ _, seq, err := WriteRecord(ctx, s, key, raw, body, etag) |
| ++ if err != nil { |
| + return nil, err |
| + } |
| ++ rec.RecordSeq = seq |
| + return &rec, nil |
| + } |
| + rec, err := update() |
| +@@ -1740,21 +1744,25 @@ |
| + return fmt.Errorf("provision: update %s/%s/%s: no etag \u2014 an update must be guarded by the read it was decided from", |
| + rec.Account, rec.App, rec.Host) |
| + } |
| +- before, currentETag, err := GetRecordETag(ctx, s, rec.Account, rec.App, rec.Host) |
| ++ prior, currentETag, err := s.Get(ctx, RecordKey(rec.Account, rec.App, rec.Host)) |
| + if err != nil { |
| + return err |
| + } |
| + if currentETag != etag { |
| + return release.ErrCASConflict |
| + } |
| +- if !protectedFieldsEqual(*before, rec) { |
| ++ before, err := DecodeRecord(prior) |
| ++ if err != nil { |
| ++ return fmt.Errorf("provision: record %s/%s/%s: %w", rec.Account, rec.App, rec.Host, err) |
| ++ } |
| ++ if !protectedFieldsEqual(before, rec) { |
| + return ErrMaintenanceProjection |
| + } |
| + body, err := json.Marshal(rec) |
| + if err != nil { |
| + return err |
| + } |
| +- _, err = s.Put(ctx, RecordKey(rec.Account, rec.App, rec.Host), body, etag) |
| ++ _, _, err = WriteRecord(ctx, s, RecordKey(rec.Account, rec.App, rec.Host), prior, body, etag) |
| + return err |
| + } |
| + |
| ``` |
| #### Patch: `internal/provision/sidecar.go` |
| @@ -6244,11 +6653,20 @@ func decideMoveStart(ctx context.Context, w *ControlWriter, puts *moveStartPuts, |
| // identity is never guessed at, the same discipline GetRecord applies to |
| // instances. |
| type SidecarRecord struct { |
| -+ // RecordSeq is advanced by the claim-on record store; absent is legacy zero. |
| ++ // RecordSeq is advanced by every record writer once present; absent is legacy zero. |
| + RecordSeq uint64 `json:"record_seq,omitempty"` |
| Acquisition string `json:"acq,omitempty"` |
| Schema int `json:"schema"` // 1 |
| Name string `json:"name"` |
| +@@ -325,7 +327,7 @@ |
| + if err != nil { |
| + return err |
| + } |
| +- _, err = s.Put(ctx, SidecarRecordKey(rec.Account, rec.App, rec.Name), body, "") |
| ++ _, _, err = WriteRecord(ctx, s, SidecarRecordKey(rec.Account, rec.App, rec.Name), []byte(`{}`), body, "") |
| + return err |
| + } |
| + |
| ``` |
| #### Patch: `internal/hostops/record_writes.go` |
| @@ -6256,7 +6674,29 @@ func decideMoveStart(ctx context.Context, w *ControlWriter, puts *moveStartPuts, |
| ```diff |
| --- a/internal/hostops/record_writes.go |
| +++ b/internal/hostops/record_writes.go |
| -@@ -91,7 +91,7 @@ |
| +@@ -5,10 +5,10 @@ |
| + "crypto/ed25519" |
| + "encoding/json" |
| + "io" |
| +- "strings" |
| + "time" |
| + |
| + "github.com/carlosframework/platform/internal/hostclaim" |
| ++ "github.com/carlosframework/platform/internal/provision" |
| + "github.com/carlosframework/platform/internal/release" |
| + ) |
| + |
| +@@ -43,8 +43,7 @@ |
| + } |
| + |
| + func provisioningKey(key string) bool { |
| +- p := strings.Split(key, "/") |
| +- return len(p) == 5 && p[0] == "apps" && (p[3] == "instances" || p[3] == "sidecars") && strings.HasSuffix(p[4], ".json") |
| ++ return provision.IsRecordKey(key) |
| + } |
| + |
| + func (s conditionalRecordStore) Get(ctx context.Context, key string) ([]byte, string, error) { |
| +@@ -91,7 +90,7 @@ |
| return s.putLifecycle(ctx, key, body, etag, permit) |
| } |
| if permit, ok := ctx.Value(effectPermitKey{}).(effectPermit); ok && (permit.Resource == key || permit.Resource == "control-object:"+key) { |
| @@ -6265,7 +6705,7 @@ func decideMoveStart(ctx context.Context, w *ControlWriter, puts *moveStartPuts, |
| } |
| // Not-found is never turned into create outside the record effect. |
| if etag == "" { |
| -@@ -131,7 +131,7 @@ |
| +@@ -131,7 +130,7 @@ |
| if obs.Absent || obs.Claim.Op.Kind != hostclaim.KindServing || !matches(current, obs.Claim.Holder) { |
| return "", ErrPending |
| } |
| @@ -6281,22 +6721,27 @@ func decideMoveStart(ctx context.Context, w *ControlWriter, puts *moveStartPuts, |
| ```diff |
| --- a/internal/hostops/lifecycle_record.go |
| +++ b/internal/hostops/lifecycle_record.go |
| -@@ -21,7 +21,13 @@ |
| +@@ -21,7 +21,7 @@ |
| return nil, ErrIdentity |
| } |
| h := o.Claim.Holder |
| - return provision.SetRecordMove(context.WithValue(ctx, lifecycleRecordKey{}, lifecycleRecordPermit{o}), s, h.Account, h.App, o.Host, mut) |
| -+ rec, err := provision.SetRecordMove(context.WithValue(ctx, lifecycleRecordKey{}, lifecycleRecordPermit{o}), s, h.Account, h.App, o.Host, mut) |
| -+ if err == nil { |
| -+ // The claim-on store advances the serialized counter after the |
| -+ // mutation callback. Return that same counter to lifecycle callers. |
| -+ rec.RecordSeq++ |
| -+ } |
| -+ return rec, err |
| ++ return provision.SetRecordMove(context.WithValue(provision.WithRecordSequence(ctx), lifecycleRecordKey{}, lifecycleRecordPermit{o}), s, h.Account, h.App, o.Host, mut) |
| } |
| func (s conditionalRecordStore) putLifecycle(ctx context.Context, key string, body []byte, etag string, p lifecycleRecordPermit) (string, error) { |
| -@@ -131,7 +137,7 @@ |
| +@@ -96,6 +96,10 @@ |
| + return "", ErrIdentity |
| + } |
| + } |
| ++ // The provision writer owns this field; WriteRecord validates its exact |
| ++ // base and prepared counter at the final conditional Put. |
| ++ delete(before, "record_seq") |
| ++ delete(next, "record_seq") |
| + delete(before, "move") |
| + delete(next, "move") |
| + delete(before, "region") |
| +@@ -131,7 +135,7 @@ |
| } |
| // Accepted work drains under closed admission; expiry does not revoke it. |
| MarkAppMutationIssued(ctx) |
| @@ -6601,6 +7046,1146 @@ func decideMoveStart(ctx context.Context, w *ControlWriter, puts *moveStartPuts, |
| ``` |
| +#### Patch: `internal/hostops/operations.go` |
| + |
| +```diff |
| +--- a/internal/hostops/operations.go |
| ++++ b/internal/hostops/operations.go |
| +@@ -8,6 +8,7 @@ |
| + "time" |
| + |
| + "github.com/carlosframework/platform/internal/hostclaim" |
| ++ "github.com/carlosframework/platform/internal/provision" |
| + "github.com/carlosframework/platform/internal/release" |
| + ) |
| + |
| +@@ -70,7 +71,7 @@ |
| + return c.Complete(ctx, obs.Host, id) |
| + } |
| + // No raw record effect precedes the durable claim intent. |
| +- if _, err = c.Store.Put(ctx, r.Holder.Key, r.Body, ""); err != nil { |
| ++ if _, _, err = provision.WriteRecord(provision.WithRecordSequence(ctx), c.Store, r.Holder.Key, []byte(`{}`), r.Body, ""); err != nil { |
| + if !errors.Is(err, release.ErrCASConflict) { |
| + return obs, err |
| + } |
| +@@ -86,7 +87,7 @@ |
| + if !removed { |
| + return obs, ErrPending |
| + } |
| +- if _, err = c.Store.Put(ctx, r.Holder.Key, r.Body, ""); err != nil { |
| ++ if _, _, err = provision.WriteRecord(provision.WithRecordSequence(ctx), c.Store, r.Holder.Key, []byte(`{}`), r.Body, ""); err != nil { |
| + return obs, err |
| + } |
| + } |
| +``` |
| + |
| +#### Patch: `internal/hostops/create_effects.go` |
| + |
| +```diff |
| +--- a/internal/hostops/create_effects.go |
| ++++ b/internal/hostops/create_effects.go |
| +@@ -9,6 +9,7 @@ |
| + |
| + "github.com/carlosframework/platform/internal/domains" |
| + "github.com/carlosframework/platform/internal/hostclaim" |
| ++ "github.com/carlosframework/platform/internal/provision" |
| + "github.com/carlosframework/platform/internal/release" |
| + ) |
| + |
| +@@ -99,7 +100,7 @@ |
| + return err |
| + } |
| + _, err = runner.Run(ctx, Effect{ID: op.Intent["record_effect"], Resource: h.Key, Action: "record-put", Inputs: map[string]string{"body": string(r.Body)}}, func(ctx context.Context) (string, error) { |
| +- etag, err := a.Store.Put(ctx, h.Key, r.Body, "") |
| ++ etag, _, err := provision.WriteRecord(provision.WithRecordSequence(ctx), a.Store, h.Key, []byte(`{}`), r.Body, "") |
| + if !errors.Is(err, release.ErrCASConflict) { |
| + return etag, err |
| + } |
| +``` |
| + |
| +#### Patch: `accounts.go` |
| + |
| +```diff |
| +--- a/accounts.go |
| ++++ b/accounts.go |
| +@@ -349,6 +349,9 @@ |
| + // verb, so it is resolved the way the runtime resolves them and not by |
| + // assuming a prefix. |
| + plan, srcAcct, hadProduction := migrationPlan(ctx, store, *app, *from, *to, dst) |
| ++ if err := validateMigrationRecords(ctx, store, plan); err != nil { |
| ++ log.Fatal(err) |
| ++ } |
| + if len(plan) == 0 { |
| + mutedf(os.Stdout, "nothing under %s \u2014 nothing to copy\n", release.AppPrefix(srcAcct, *app)) |
| + } |
| +@@ -413,7 +416,7 @@ |
| + } |
| + continue |
| + } |
| +- if _, err := store.Put(ctx, c.to, body, ""); err != nil { |
| ++ if _, err := putMigrationObject(ctx, store, c.to, body); err != nil { |
| + cur, _, gerr := store.Get(ctx, c.to) |
| + if gerr != nil { |
| + log.Fatalf("accounts migrate: write %s: %v", c.to, err) |
| +``` |
| + |
| +#### New file: `accounts_record_sequence.go` |
| + |
| +```go |
| +package main |
| + |
| +import ( |
| + "context" |
| + "fmt" |
| + |
| + "github.com/carlosframework/platform/internal/provision" |
| + "github.com/carlosframework/platform/internal/release" |
| +) |
| + |
| +// Legacy migrations retain their create-only bytes. A populated sequence |
| +// cannot be transplanted into another key as a new incarnation; that needs |
| +// the claim create protocol, not a raw copy of archived authority. |
| +func putMigrationObject(ctx context.Context, store release.Store, key string, body []byte) (string, error) { |
| + if provision.IsRecordKey(key) { |
| + if err := migrationRecordBytes(key, body); err != nil { |
| + return "", err |
| + } |
| + tag, _, err := provision.WriteRecord(ctx, store, key, []byte(`{}`), body, "") |
| + return tag, err |
| + } |
| + return store.Put(ctx, key, body, "") |
| +} |
| + |
| +// Preflight precedes every copy and also runs for --dry-run. The write helper |
| +// repeats the check because a source can change after preflight. |
| +func validateMigrationRecords(ctx context.Context, store release.Store, plan []copyOp) error { |
| + for _, c := range plan { |
| + if !provision.IsRecordKey(c.to) { |
| + continue |
| + } |
| + raw, _, err := store.Get(ctx, c.from) |
| + if err != nil { |
| + return err |
| + } |
| + if err = migrationRecordBytes(c.from, raw); err != nil { |
| + return err |
| + } |
| + } |
| + return nil |
| +} |
| + |
| +func migrationRecordBytes(key string, body []byte) error { |
| + fields, _, err := provision.RecordSequence(body) |
| + if err != nil { |
| + return err |
| + } |
| + if _, present := fields["record_seq"]; present { |
| + return fmt.Errorf("accounts migrate: %s has a host-claim sequence; use the claim create protocol to create a new incarnation: %w", key, provision.ErrRecordSequence) |
| + } |
| + return nil |
| +} |
| +``` |
| + |
| +#### New file: `accounts_record_sequence_test.go` |
| + |
| +```go |
| +package main |
| + |
| +import ( |
| + "bytes" |
| + "context" |
| + "errors" |
| + "os" |
| + "testing" |
| + |
| + "github.com/carlosframework/platform/internal/provision" |
| + "github.com/carlosframework/platform/internal/release" |
| +) |
| + |
| +func TestPlan9B8MigrationCannotReplaySequencedRecord(t *testing.T) { |
| + ctx := context.Background() |
| + for _, family := range []string{"instances", "sidecars"} { |
| + t.Run(family, func(t *testing.T) { |
| + store := release.NewDirStore(t.TempDir()) |
| + key := "apps/bab/app/" + family + "/record.json" |
| + legacy := []byte("{\"schema\": 1}\n") |
| + if _, err := putMigrationObject(ctx, store, key, legacy); err != nil { |
| + t.Fatal(err) |
| + } |
| + raw, _, err := store.Get(ctx, key) |
| + if err != nil || !bytes.Equal(raw, legacy) { |
| + t.Fatalf("legacy bytes: %s %v", raw, err) |
| + } |
| + key = "apps/bab/app/" + family + "/new.json" |
| + if _, err := putMigrationObject(ctx, store, key, []byte(`{"record_seq":9}`)); !errors.Is(err, provision.ErrRecordSequence) { |
| + t.Fatalf("replayed sequence: %v", err) |
| + } |
| + if _, _, err := store.Get(ctx, key); !errors.Is(err, os.ErrNotExist) { |
| + t.Fatalf("replay wrote: %v", err) |
| + } |
| + }) |
| + } |
| +} |
| + |
| +func TestPlan9B8MigrationPreflightRefusesBeforeAnyCopy(t *testing.T) { |
| + ctx := context.Background() |
| + store := release.NewDirStore(t.TempDir()) |
| + source := "apps/bab/app/instances/source.json" |
| + dest := "apps/cac/app/instances/source.json" |
| + for _, body := range []string{`{"record_seq":0}`, `{"record_seq":9}`} { |
| + if err := store.Delete(ctx, source); err != nil { |
| + t.Fatal(err) |
| + } |
| + if _, err := store.Put(ctx, source, []byte(body), ""); err != nil { |
| + t.Fatal(err) |
| + } |
| + plan := []copyOp{{from: "apps/bab/app/channel.json", to: "apps/cac/app/channel.json"}, {from: source, to: dest}} |
| + if err := validateMigrationRecords(ctx, store, plan); !errors.Is(err, provision.ErrRecordSequence) { |
| + t.Fatalf("preflight: %v", err) |
| + } |
| + keys, err := store.List(ctx, "apps/cac/") |
| + if err != nil || len(keys) != 0 { |
| + t.Fatalf("preflight wrote: %v %v", keys, err) |
| + } |
| + } |
| +} |
| +``` |
| + |
| +#### New file: `internal/provision/plan9_b8_census_test.go` |
| + |
| +```go |
| +package provision |
| + |
| +// Plan 9 B8 record-write census. |
| +// |
| +// Every production top-level declaration in the repository that |
| +// - selects, defines, or merely names Put, PutFrom, PutIfAbsent or |
| +// CopyVersion (calls, method values and expressions, interface and struct |
| +// members, string names; plus the derived PutObjectFrom, PutReplace, |
| +// PutIfAbsentVersioned[At] and CopyVersioned[At]), or |
| +// - touches a record-key constructor (RecordKey, SidecarRecordKey, |
| +// RecordsPrefix, SidecarsPrefix, SidecarManifestKey, ParseRecordKey, |
| +// ParseSidecarKey, WriteRecord, putSequencedRecord and the typed |
| +// CreateRecord/UpdateRecord/SetRecord*/CreateSidecarRecord API), or |
| +// - holds a record path literal ("instances", "sidecars", "instances/", |
| +// "sidecars/", "instances.json", "sidecars.json"), |
| +// |
| +// must appear in plan9B8CensusInventory with the reviewed class, the exact |
| +// set of site kinds, and the sha256 of its source. A new, changed, removed or |
| +// unlisted unit fails the test. The test never rewrites the inventory: |
| +// Review the changed writer and its callers before updating the explicit |
| +// inventory below; never bless a changed digest just to make this test pass. |
| +// |
| +// Matching is by name, not by type. A same-named method on any receiver, a |
| +// local alias, a method value or a function field all count; that is the |
| +// point. It cannot see a key assembled from fragments none of which is a |
| +// listed identifier or literal; the digest pins every unit it can see. |
| + |
| +import ( |
| + "crypto/sha256" |
| + "encoding/hex" |
| + "fmt" |
| + "go/ast" |
| + "go/parser" |
| + "go/token" |
| + "io/fs" |
| + "os" |
| + "path/filepath" |
| + "sort" |
| + "strings" |
| + "testing" |
| +) |
| + |
| +type censusEntry struct{ File, Unit, Class, Kinds, Digest string } |
| + |
| +var censusMethods = map[string]bool{ |
| + "Put": true, "PutFrom": true, "PutIfAbsent": true, "CopyVersion": true, |
| + "PutObjectFrom": true, "PutReplace": true, |
| + "PutIfAbsentVersioned": true, "PutIfAbsentVersionedAt": true, |
| + "CopyVersioned": true, "CopyVersionedAt": true, |
| +} |
| + |
| +// Lower-cased identifier suffixes / exact names that mark a record-key or |
| +// typed-record-writer touchpoint. Keep these names aligned with the APIs above. |
| +var ( |
| + censusKeySuffixes = []string{"recordkey", "recordsprefix", "sidecarsprefix", "sidecarkey", "sidecarmanifestkey", "writerecord", "sequencedrecord"} |
| + censusKeyExact = map[string]bool{"createrecord": true, "updaterecord": true, "setrecordregion": true, "setrecordmove": true, "createsidecarrecord": true, "claimrawupdaterecord": true} |
| + censusLitWords = []string{"instances", "sidecars"} |
| +) |
| + |
| +var censusClasses = map[string]bool{ |
| + "record-primitive": true, "record-writer": true, "record-writer-raw": true, |
| + "record-transport": true, "versioned-transport": true, "foreign-recordkey": true, |
| + "nonrecord": true, "key-site": true, "reference": true, "testsupport": true, |
| +} |
| + |
| +func censusKeyIdent(name string) bool { |
| + low := strings.ToLower(name) |
| + if censusKeyExact[low] { |
| + return true |
| + } |
| + for _, s := range censusKeySuffixes { |
| + if strings.HasSuffix(low, s) { |
| + return true |
| + } |
| + } |
| + return false |
| +} |
| + |
| +func censusRecvName(e ast.Expr) string { |
| + for { |
| + switch x := e.(type) { |
| + case *ast.StarExpr: |
| + e = x.X |
| + case *ast.ParenExpr: |
| + e = x.X |
| + case *ast.IndexExpr: |
| + e = x.X |
| + case *ast.IndexListExpr: |
| + e = x.X |
| + case *ast.Ident: |
| + return x.Name |
| + default: |
| + return "?" |
| + } |
| + } |
| +} |
| + |
| +func censusSpecName(s ast.Spec) string { |
| + switch s := s.(type) { |
| + case *ast.ValueSpec: |
| + return s.Names[0].Name |
| + case *ast.TypeSpec: |
| + return s.Name.Name |
| + } |
| + return "?" |
| +} |
| + |
| +// censusParse returns one entry (Class empty) per top-level unit of src that |
| +// has at least one site kind. path is the slash-separated repo-relative name. |
| +func censusParse(path string, src []byte) ([]censusEntry, error) { |
| + fset := token.NewFileSet() |
| + f, err := parser.ParseFile(fset, path, src, 0) |
| + if err != nil { |
| + return nil, err |
| + } |
| + var out []censusEntry |
| + seen := map[string]int{} |
| + grouped := map[string]int{} |
| + for _, decl := range f.Decls { |
| + var name string |
| + switch d := decl.(type) { |
| + case *ast.FuncDecl: |
| + name = d.Name.Name |
| + if d.Recv != nil && len(d.Recv.List) == 1 { |
| + name = censusRecvName(d.Recv.List[0].Type) + "." + name |
| + } |
| + case *ast.GenDecl: |
| + if d.Tok == token.IMPORT { |
| + continue |
| + } |
| + if d.Lparen.IsValid() { |
| + grouped[d.Tok.String()]++ |
| + name = fmt.Sprintf("%s#%d", d.Tok, grouped[d.Tok.String()]) |
| + } else { |
| + name = d.Tok.String() + " " + censusSpecName(d.Specs[0]) |
| + } |
| + default: |
| + continue |
| + } |
| + seen[name]++ |
| + if seen[name] > 1 { |
| + name = fmt.Sprintf("%s#%d", name, seen[name]) |
| + } |
| + kinds := map[string]bool{} |
| + done := map[*ast.Ident]bool{} |
| + name2 := func(n string, method string) { |
| + if censusMethods[n] { |
| + kinds[method+":"+n] = true |
| + } |
| + if censusKeyIdent(n) { |
| + kinds["key:"+n] = true |
| + } |
| + } |
| + ast.Inspect(decl, func(n ast.Node) bool { |
| + switch n := n.(type) { |
| + case *ast.FuncDecl: |
| + name2(n.Name.Name, "def") |
| + done[n.Name] = true |
| + case *ast.SelectorExpr: |
| + name2(n.Sel.Name, "sel") |
| + done[n.Sel] = true |
| + case *ast.Ident: |
| + if !done[n] { |
| + name2(n.Name, "id") |
| + } |
| + case *ast.BasicLit: |
| + if n.Kind == token.STRING && len(n.Value) >= 2 { |
| + inner := n.Value[1 : len(n.Value)-1] |
| + if censusMethods[inner] { |
| + kinds["str:"+inner] = true |
| + } |
| + for _, w := range censusLitWords { |
| + if inner == w || strings.Contains(inner, w+"/") || strings.Contains(inner, w+".json") { |
| + kinds["lit:"+w] = true |
| + } |
| + } |
| + } |
| + } |
| + return true |
| + }) |
| + if len(kinds) == 0 { |
| + continue |
| + } |
| + ks := make([]string, 0, len(kinds)) |
| + for k := range kinds { |
| + ks = append(ks, k) |
| + } |
| + sort.Strings(ks) |
| + sum := sha256.Sum256(src[fset.Position(decl.Pos()).Offset:fset.Position(decl.End()).Offset]) |
| + out = append(out, censusEntry{File: path, Unit: name, Kinds: strings.Join(ks, ","), Digest: hex.EncodeToString(sum[:])}) |
| + } |
| + return out, nil |
| +} |
| + |
| +func censusRoot(t *testing.T) string { |
| + t.Helper() |
| + dir, err := os.Getwd() |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + for { |
| + if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil { |
| + return dir |
| + } |
| + up := filepath.Dir(dir) |
| + if up == dir { |
| + t.Fatal("census: no go.mod above the working directory") |
| + } |
| + dir = up |
| + } |
| +} |
| + |
| +// censusProduction parses every non-test .go file under root (testdata, |
| +// vendor, dot- and underscore-directories excluded), build tags ignored. |
| +func censusProduction(root string) (map[string]censusEntry, error) { |
| + got := map[string]censusEntry{} |
| + err := filepath.WalkDir(root, func(p string, d fs.DirEntry, err error) error { |
| + if err != nil { |
| + return err |
| + } |
| + if d.IsDir() { |
| + n := d.Name() |
| + if p != root && (n == "testdata" || n == "vendor" || strings.HasPrefix(n, ".") || strings.HasPrefix(n, "_")) { |
| + return filepath.SkipDir |
| + } |
| + return nil |
| + } |
| + if !strings.HasSuffix(p, ".go") || strings.HasSuffix(p, "_test.go") { |
| + return nil |
| + } |
| + src, err := os.ReadFile(p) |
| + if err != nil { |
| + return err |
| + } |
| + rel, err := filepath.Rel(root, p) |
| + if err != nil { |
| + return err |
| + } |
| + units, err := censusParse(filepath.ToSlash(rel), src) |
| + if err != nil { |
| + return err |
| + } |
| + for _, u := range units { |
| + got[u.File+":"+u.Unit] = u |
| + } |
| + return nil |
| + }) |
| + return got, err |
| +} |
| + |
| +// censusDiff compares the reviewed inventory with what the source shows. |
| +func censusDiff(inv []censusEntry, got map[string]censusEntry) []string { |
| + var problems []string |
| + listed := map[string]censusEntry{} |
| + for _, e := range inv { |
| + id := e.File + ":" + e.Unit |
| + if _, dup := listed[id]; dup { |
| + problems = append(problems, "duplicate inventory entry "+id) |
| + } |
| + listed[id] = e |
| + } |
| + for id, g := range got { |
| + e, ok := listed[id] |
| + switch { |
| + case !ok: |
| + problems = append(problems, fmt.Sprintf("unlisted site %s [%s]: review it, classify it, and update its explicit inventory entry", id, g.Kinds)) |
| + case e.Kinds != g.Kinds: |
| + problems = append(problems, fmt.Sprintf("changed site %s: kinds were [%s], now [%s]", id, e.Kinds, g.Kinds)) |
| + case e.Digest != g.Digest: |
| + problems = append(problems, fmt.Sprintf("changed site %s: source differs from the reviewed digest", id)) |
| + } |
| + } |
| + for id := range listed { |
| + if _, ok := got[id]; !ok { |
| + problems = append(problems, "stale inventory entry "+id+": no such unit with a write site any more") |
| + } |
| + } |
| + sort.Strings(problems) |
| + return problems |
| +} |
| + |
| +func censusKindNames(kinds, prefix string) []string { |
| + var out []string |
| + for _, k := range strings.Split(kinds, ",") { |
| + if rest, ok := strings.CutPrefix(k, prefix); ok { |
| + out = append(out, rest) |
| + } |
| + } |
| + return out |
| +} |
| + |
| +// censusPolicy holds the classification invariants the inventory must satisfy. |
| +func censusPolicy(inv []censusEntry) []string { |
| + var problems []string |
| + primitive := 0 |
| + for _, e := range inv { |
| + id := e.File + ":" + e.Unit |
| + if !censusClasses[e.Class] { |
| + problems = append(problems, fmt.Sprintf("%s: unknown class %q", id, e.Class)) |
| + continue |
| + } |
| + var viaPrimitive, directPut, ctor bool |
| + for _, k := range censusKindNames(e.Kinds, "key:") { |
| + if k == "WriteRecord" || k == "putSequencedRecord" { |
| + viaPrimitive = true |
| + } |
| + if k != "SidecarManifestKey" { |
| + ctor = true |
| + } // App manifests are not host records. |
| + } |
| + if len(censusKindNames(e.Kinds, "lit:")) != 0 { |
| + ctor = true |
| + } |
| + for _, k := range censusKindNames(e.Kinds, "sel:") { |
| + if censusMethods[k] { |
| + directPut = true |
| + } |
| + } |
| + switch e.Class { |
| + case "record-primitive": |
| + primitive++ |
| + if id != "internal/provision/record_sequence.go:WriteRecord" { |
| + problems = append(problems, id+": only provision.WriteRecord may be the record-key Put primitive") |
| + } |
| + case "record-writer-raw": |
| + problems = append(problems, id+": writes record keys without WriteRecord") |
| + case "record-writer": |
| + typed := viaPrimitive |
| + for _, k := range censusKindNames(e.Kinds, "key:") { |
| + if censusKeyExact[strings.ToLower(k)] { |
| + typed = true |
| + } |
| + } |
| + if !typed { |
| + problems = append(problems, id+": record-writer must reach the store through WriteRecord or the typed provision API") |
| + } |
| + } |
| + if e.Class == "nonrecord" { |
| + for _, k := range strings.Split(e.Kinds, ",") { |
| + low := strings.ToLower(strings.TrimPrefix(k, "key:")) |
| + touches := strings.HasPrefix(k, "lit:") || censusKeyExact[low] |
| + for _, suf := range []string{"recordkey", "recordsprefix", "sidecarsprefix", "sidecarkey", "writerecord", "sequencedrecord"} { |
| + touches = touches || strings.HasPrefix(k, "key:") && strings.HasSuffix(low, suf) |
| + } |
| + if touches && (strings.HasPrefix(k, "lit:") || strings.HasPrefix(k, "key:")) { |
| + problems = append(problems, fmt.Sprintf("%s: class nonrecord but it mentions %s; classify it as what it is", id, k)) |
| + } |
| + } |
| + } |
| + if ctor && directPut && !viaPrimitive && e.Class != "record-writer-raw" && e.Class != "foreign-recordkey" { |
| + problems = append(problems, id+": builds a record key and Puts directly; it must be record-writer-raw (fails) or foreign-recordkey with a reason") |
| + } |
| + } |
| + if primitive != 1 { |
| + problems = append(problems, fmt.Sprintf("want exactly one record-primitive, have %d", primitive)) |
| + } |
| + return problems |
| +} |
| + |
| +func TestPlan9B8RecordWriteCensus(t *testing.T) { |
| + root := censusRoot(t) |
| + got, err := censusProduction(root) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if len(got) < 100 { |
| + t.Fatalf("census found only %d units under %s; the walk is broken", len(got), root) |
| + } |
| + problems := append(censusDiff(plan9B8CensusInventory, got), censusPolicy(plan9B8CensusInventory)...) |
| + if len(problems) > 0 { |
| + t.Fatalf("record-write census rejected %d site(s); update the explicit inventory only after reviewing the source:\n %s", len(problems), strings.Join(problems, "\n ")) |
| + } |
| +} |
| + |
| +// The census must reject each way a new record write could be introduced. |
| +func TestPlan9B8CensusRejectsNewSites(t *testing.T) { |
| + const base = `package x |
| + |
| +import "context" |
| + |
| +func Reviewed(ctx context.Context, s Store, k string) error { |
| + _, err := s.Put(ctx, k, nil, "") |
| + return err |
| +} |
| +` |
| + observe := func(t *testing.T, src string) map[string]censusEntry { |
| + t.Helper() |
| + units, err := censusParse("x.go", []byte(src)) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + m := map[string]censusEntry{} |
| + for _, u := range units { |
| + m[u.File+":"+u.Unit] = u |
| + } |
| + return m |
| + } |
| + var inv []censusEntry |
| + for _, u := range observe(t, base) { |
| + u.Class = "nonrecord" |
| + inv = append(inv, u) |
| + } |
| + if len(inv) != 1 { |
| + t.Fatalf("baseline inventory: %+v", inv) |
| + } |
| + if p := censusDiff(inv, observe(t, base)); len(p) != 0 { |
| + t.Fatalf("unchanged source must pass, got %v", p) |
| + } |
| + cases := []struct{ name, src, want string }{ |
| + {"raw Put alias", base + "\nfunc Alias(s Store) { put := s.Put; _ = put }\n", "unlisted site x.go:Alias [sel:Put]"}, |
| + {"raw PutIfAbsent alias", base + "\nfunc AliasAbsent(s Store) { f := s.PutIfAbsent; _ = f }\n", "unlisted site x.go:AliasAbsent [sel:PutIfAbsent]"}, |
| + {"CopyVersion alias", base + "\nfunc AliasCopy(v Versioned) { f := v.CopyVersion; _ = f }\n", "unlisted site x.go:AliasCopy [sel:CopyVersion]"}, |
| + {"PutFrom alias", base + "\nfunc AliasFrom(s Store) { f := s.PutFrom; _ = f }\n", "unlisted site x.go:AliasFrom [sel:PutFrom]"}, |
| + {"method value returned", base + "\nfunc MethodValue(s Store) func(context.Context, string, []byte, string) (string, error) { return s.Put }\n", "unlisted site x.go:MethodValue [sel:Put]"}, |
| + {"method expression", base + "\nvar expr = Store.Put\n", "unlisted site x.go:var expr [sel:Put]"}, |
| + {"interface member", base + "\ntype Writer interface{ Put(string) }\n", "unlisted site x.go:type Writer [id:Put]"}, |
| + {"function field", base + "\nvar hook = struct{ Put func() }{}\n", "unlisted site x.go:var hook [id:Put]"}, |
| + {"sidecar key", base + "\nfunc Side(a, b, c string) string { return provision.SidecarRecordKey(a, b, c) }\n", "unlisted site x.go:Side [key:SidecarRecordKey]"}, |
| + {"instance key", base + "\nfunc Inst(a, b, c string) string { return provision.RecordKey(a, b, c) }\n", "unlisted site x.go:Inst [key:RecordKey]"}, |
| + {"records prefix", base + "\nfunc Pref(a, b string) string { return provision.RecordsPrefix(a, b) }\n", "unlisted site x.go:Pref [key:RecordsPrefix]"}, |
| + {"box callsite", base + "\nfunc Box(ctx context.Context, s Store, k string, b []byte) { provision.WriteRecord(ctx, s, k, nil, b, \"\") }\n", "unlisted site x.go:Box [key:WriteRecord]"}, |
| + {"typed writer callsite", base + "\nfunc Typed(ctx context.Context, s Store) { provision.SetRecordMove(ctx, s, \"a\", \"b\", \"c\", nil) }\n", "unlisted site x.go:Typed [key:SetRecordMove]"}, |
| + {"record path literal", base + "\nfunc Lit(h string) string { return \"apps/a/b/instances/\" + h + \".json\" }\n", "unlisted site x.go:Lit [lit:instances]"}, |
| + {"sidecar path literal", base + "\nconst sidecarDir = \"sidecars\"\n", "unlisted site x.go:const sidecarDir [lit:sidecars]"}, |
| + {"reflective name", base + "\nfunc Reflect(v any) { _ = reflect.ValueOf(v).MethodByName(\"Put\") }\n", "unlisted site x.go:Reflect [str:Put]"}, |
| + {"reviewed unit gains a second write", strings.Replace(base, "return err", "f := s.PutIfAbsent\n\t_ = f\n\treturn err", 1), "changed site x.go:Reviewed: kinds were [sel:Put], now [sel:Put,sel:PutIfAbsent]"}, |
| + {"reviewed unit body edited", strings.Replace(base, `nil, "")`, `nil, "*")`, 1), "changed site x.go:Reviewed: source differs from the reviewed digest"}, |
| + {"reviewed unit gains a record key", strings.Replace(base, "return err", "_ = provision.RecordKey(\"a\", \"b\", \"c\")\n\treturn err", 1), "changed site x.go:Reviewed: kinds were [sel:Put], now [key:RecordKey,sel:Put]"}, |
| + {"reviewed unit removed", "package x\n", "stale inventory entry x.go:Reviewed"}, |
| + } |
| + for _, c := range cases { |
| + t.Run(c.name, func(t *testing.T) { |
| + problems := censusDiff(inv, observe(t, c.src)) |
| + for _, p := range problems { |
| + if strings.Contains(p, c.want) { |
| + return |
| + } |
| + } |
| + t.Fatalf("want a problem containing %q, got %v", c.want, problems) |
| + }) |
| + } |
| + t.Run("record key plus direct Put is policy-rejected", func(t *testing.T) { |
| + bad := []censusEntry{{File: "x.go", Unit: "Direct", Class: "nonrecord", Kinds: "key:RecordKey,sel:Put"}} |
| + if p := censusPolicy(bad); len(p) == 0 || !strings.Contains(strings.Join(p, "\n"), "builds a record key and Puts directly") { |
| + t.Fatalf("policy accepted a direct record Put: %v", p) |
| + } |
| + }) |
| + for _, kinds := range []string{"lit:instances,sel:Put", "lit:sidecars,sel:PutObjectFrom", "key:RecordsPrefix,sel:PutIfAbsentVersionedAt", "key:SidecarRecordKey,sel:CopyVersioned"} { |
| + t.Run("transport cannot originate "+kinds, func(t *testing.T) { |
| + bad := []censusEntry{{File: "x.go", Unit: "Transport", Class: "record-transport", Kinds: kinds}} |
| + if problems := censusPolicy(bad); len(problems) == 0 || !strings.Contains(strings.Join(problems, "\n"), "builds a record key and Puts directly") { |
| + t.Fatalf("transport bypass accepted: %v", problems) |
| + } |
| + }) |
| + } |
| + t.Run("a second record-primitive is rejected", func(t *testing.T) { |
| + bad := []censusEntry{ |
| + {File: "internal/provision/record_sequence.go", Unit: "WriteRecord", Class: "record-primitive", Kinds: "key:WriteRecord,sel:Put"}, |
| + {File: "x.go", Unit: "Other", Class: "record-primitive", Kinds: "sel:Put"}, |
| + } |
| + if p := censusPolicy(bad); len(p) == 0 { |
| + t.Fatal("policy accepted a second record-key Put primitive") |
| + } |
| + }) |
| + t.Run("raw record writer is rejected", func(t *testing.T) { |
| + bad := []censusEntry{{File: "x.go", Unit: "Raw", Class: "record-writer-raw", Kinds: "key:RecordKey,sel:Put"}} |
| + if p := censusPolicy(bad); len(p) == 0 { |
| + t.Fatal("policy accepted a raw record writer") |
| + } |
| + }) |
| +} |
| + |
| +// BEGIN REVIEWED INVENTORY |
| +var plan9B8CensusInventory = []censusEntry{ |
| + {"accounts.go", "putOverwrite", "nonrecord", "sel:Put", "a85f380434a2d297f14c8068083f41ca7f9c9b735efd7d3abf8d91520fe6453c"}, |
| + {"accounts_record_sequence.go", "putMigrationObject", "record-writer", "key:IsRecordKey,key:WriteRecord,sel:Put", "c3070eea555ad682be4eb9d9aa07fd42225ffdc3621b1b517309ecf1f4bf14c2"}, |
| + {"accounts_record_sequence.go", "validateMigrationRecords", "key-site", "key:IsRecordKey", "e398e210a0ae3893ce8f0f31dad6c32e8e77c3a1214652e8152e362780535a38"}, |
| + {"apimode.go", "apiSession.instanceSetChannel", "key-site", "lit:instances", "e94374a40fc4bce37470ea5cdbdf17b0ceaa2d3b09cf8e755a5ca2fc42e98418"}, |
| + {"apimode.go", "apiSession.instanceSetUpstreams", "key-site", "lit:instances", "a4a3949526c5f722de61f11d000e230da4bad7e330d85df9ddb19abd6b234266"}, |
| + {"apimode.go", "apiSession.instanceSetEnvironment", "key-site", "lit:instances", "aae153781a31a5fa3c80120242b3ab0c11c4ecf32f849e73f8befa34282f0044"}, |
| + {"apimode.go", "apiSession.instanceSetSteering", "key-site", "lit:instances", "86180e9be148c0a27aee61748fd55b029c8d9feaf7aa0d6a131462b06188a6ab"}, |
| + {"apimode.go", "apiSession.instanceSetGate", "key-site", "lit:instances", "d2105918b5b1db014678fa6127a8cb0d84c2a473a247efd94a1a48b8928d484e"}, |
| + {"apimode.go", "apiSession.instanceDelete", "key-site", "lit:instances", "fcb30a3d930a508bd061ff7ed3f615371271891e5c75b0e9d5c00bd723369888"}, |
| + {"apimode.go", "apiSession.instanceGet", "key-site", "lit:instances", "9ee7812feb391a0c839ae7448534bd7f2e2a3a29e3fff1dbe7faa110024bc9a6"}, |
| + {"apimode.go", "apiSession.sidecarGet", "key-site", "lit:sidecars", "1a35497dbb705df114430141c124296fb6be30690c07eeedb50ee3cc2b33de93"}, |
| + {"apimode.go", "apiSession.sidecarDelete", "key-site", "lit:sidecars", "f23690fec191a001df364d3f39d06d3c287ce95f8a2b1b4ea94306d3536fbb3c"}, |
| + {"apimode.go", "apiSession.sidecarWake", "key-site", "lit:sidecars", "9d1239f5d3c2977de9f406ede15ca97756ff2929268857047a58c28d344d9868"}, |
| + {"cli.go", "runInstances", "key-site", "lit:instances", "cbd14f548169ef95e10a75ed272c9178ed687f211c55f4152f638d11a8c91a5f"}, |
| + {"cli.go", "runRoute", "key-site", "lit:instances", "9b314e170a811ad7cd3732d93a221888446f97ce6942dc0f6001c5df3009a3f5"}, |
| + {"cli.go", "runRouteHibernate", "key-site", "key:SidecarManifestKey", "c151cae2cbc57943e9a5d0a5dac611823d769a6b651d5dbc2fe86182f6e098b1"}, |
| + {"cli.go", "RunStatus", "key-site", "lit:instances", "3b9936cf0f29f57f06c378a334e3118096679dfdadab7aad45b1f6c7b5841562"}, |
| + {"domains_cli.go", "runDomainsList", "key-site", "lit:instances", "f7aefeff56b0be7b50ee82bd250ada08ae9a095b7a62f577ead70846e00b61ad"}, |
| + {"main.go", "main", "key-site", "lit:instances,lit:sidecars", "2fe548f453c84b9500d9665542ddc15aacd91c659901d19f1a394d3acce2e9bf"}, |
| + {"schedule.go", "renderScheduleList", "key-site", "lit:instances", "af6d666083fbbc753eecc98809d3d307344cc15110362a7e93afab573749ea6e"}, |
| + {"internal/activator/layout.go", "LayoutInputs.sockets", "key-site", "lit:instances", "1d11ac5ac5be6a42e676e064771141fe8f4367c78cbb7e5c5cca072f40c27c42"}, |
| + {"internal/activator/paths.go", "ExecBackend.pathsFor", "key-site", "lit:instances", "cbc01865e27934cb38baab9329a0867b899149531b4706a533a6956afc7c71b3"}, |
| + {"internal/analytics/day.go", "UpsertDay", "nonrecord", "sel:Put", "c29f6c1c71ea18299291c1167e01c6aa9844f07acb52a2771ec62fa327c71d88"}, |
| + {"internal/appconfig/configack.go", "WriteConfigAck", "nonrecord", "sel:Put", "211c8417bdbd6c6721fc503db085e9df073adeba623d9daa1e226554760ef5d1"}, |
| + {"internal/appconfig/generation.go", "claimRawTouchGeneration", "nonrecord", "sel:Put", "313a5de17aee1b9ed6a344f6b785637c6978b60552a2243ef208c28eb8401a02"}, |
| + {"internal/appconfig/store.go", "applyAt", "nonrecord", "sel:Put", "d234e359f44951240e6c052d87a17587845fb263ef9499dae39dae6c5c763e47"}, |
| + {"internal/appconfig/store.go", "claimRawSetExecDeliveryAt", "nonrecord", "sel:Put", "ebbb8b98849e9fab8c4a15b132cd8f6fb26dff074c2cdb51247fe1e4b1c53a16"}, |
| + {"internal/appconfig/syncexec.go", "reapNestedConfigEnv", "key-site", "lit:instances", "559691b5a516fa240e3a9d2490669ea52281fb085e728d2938970ae439c7ded4"}, |
| + {"internal/awsinv/ec2.go", "ec2Resources", "key-site", "lit:instances", "3fc9a7d769c2e8bf51b7e1809488b7813a5a4b8adffd86a61081649638c14c54"}, |
| + {"internal/boxid/record.go", "Publish", "nonrecord", "sel:Put", "c60d1cf14e758de490c0824b635eb5d6ac54e24b5dd05c99a576a8a03d46ead7"}, |
| + {"internal/boxvolumes/volumes.go", "Write", "nonrecord", "sel:Put", "70ec487d1180d4abe18031d07f49ea98ff7d7fb222403c1ed9304c00429e7825"}, |
| + {"internal/console/account.go", "WriteAccountManifest", "nonrecord", "sel:Put", "f6f1ec0f2aa488ab51850ccf47753e37736d65b94708a1cf25ee0af1152ce251"}, |
| + {"internal/console/apidbclaim.go", "Server.dbClaim", "key-site", "key:RecordKey", "9901c67966f4f932b0dd4acff8b243be5d91bcef55fff0c34ec03ba080b87983"}, |
| + {"internal/console/apidomains.go", "Server.handleAPIDomainAttach", "key-site", "lit:instances", "f235eb06f14a7a2d74b69214076329b7d506eb5d49532ba74aa78b61e90fc5e2"}, |
| + {"internal/console/apidoorkeys.go", "Server.publishDoorKeysLocked", "foreign-recordkey", "key:RecordKey,sel:Put", "0fc498cd4af50570a0b7ed3ff71994213cab40040f6701a303bdf85d94f958da"}, |
| + {"internal/console/apiinstancemove.go", "Server.moveTransitionSaying", "record-writer", "key:SetRecordMove", "d1f7162eecdc234607c708a83e9633cdf77cc6931bac6754fb51042dd8409377"}, |
| + {"internal/console/apiinstancemove.go", "Server.handleAPIInstanceMoveRequest", "record-writer", "key:SetRecordMove,lit:instances", "708ef4614cf5fd9c1ad382cb79da55af0b1192a235fd719765affda92aaf1668"}, |
| + {"internal/console/apiinstancemove.go", "Server.registerMovesPhase1Routes", "key-site", "lit:instances", "5af589d288ba22856f631481c40cc001aac342ea74cee6ee0464ec77d0320bbc"}, |
| + {"internal/console/apiinstances.go", "Server.instanceManifest", "key-site", "lit:instances", "e6160ffe84abda9163527ccb33e2ffff2109b35908adb2f3a754534325658fdf"}, |
| + {"internal/console/apiinstances.go", "Server.handleAPIInstanceList", "key-site", "lit:instances", "54da4ed10eb3b8cce0dd64407221befa232f135b81b9c76840aa42bcd5b93106"}, |
| + {"internal/console/apiinstances.go", "Server.handleAPIInstanceCreate", "record-writer", "key:CreateRecord", "20f862446f119f25d7d72eab6edd1b7cfb1528afd5b4c352e0cb8442f5080b13"}, |
| + {"internal/console/apiinstances.go", "Server.handleAPIInstanceUpdate", "record-writer", "key:UpdateRecord", "4b792c410399631eb3b38c78829df85dda045651bbc32df769767cf52f015b6d"}, |
| + {"internal/console/apiscan.go", "Server.writeScanRecord", "nonrecord", "sel:Put", "c3c092f4b7be9bf6bf09d0fe1e2c9fe52892ee97e57f6c7ab964b85fe36fb5c6"}, |
| + {"internal/console/apisidecars.go", "Server.handleAPISidecarList", "key-site", "lit:sidecars", "57bed5957395011a0c092731f1f8b007ce92e09119ba0db7d694e255b2fecc0d"}, |
| + {"internal/console/apisidecars.go", "Server.handleAPISidecarCreate", "record-writer", "key:CreateSidecarRecord", "a7d5eb534b540499b9355b455966e733d61c904be5cc5a76a168833df59de1c3"}, |
| + {"internal/console/apistatusinstances.go", "Server.handleAPIStatusInstances", "key-site", "lit:instances", "2b4b9a2bea5f2fc5727869a5dcb8ae96c020e72571621f19898b195ca3b61aaa"}, |
| + {"internal/console/apisystemupdate.go", "Server.mirrorSystemArtifact", "nonrecord", "sel:Put", "76a8dcb558e3723ad5774788da37c22234c229dc3257ded5a6c7b04746c69c2f"}, |
| + {"internal/console/appsettings.go", "Server.handleAppGate", "record-writer", "key:UpdateRecord", "011431f5459529c0abf354edc2cb772cb6a7f2816d3bb50d91598d7d241838ce"}, |
| + {"internal/console/authcli.go", "Server.registerCLIRoutes", "key-site", "lit:instances,lit:sidecars", "153830d24ff9dc61725a0ee8ca7077720783a3a2f589f5ca384f0cbd83958334"}, |
| + {"internal/console/browserrestart.go", "Server.handleBrowserRestart", "key-site", "lit:instances", "955d71813504f2cfec1e1f64817d5aa5a921aa38e1044c0e903d2cfbe4d073f4"}, |
| + {"internal/console/channelmove.go", "Server.handleAPIInstanceChannel", "record-writer", "key:UpdateRecord", "91a2b3157bde9af832e8d95b17d0c384910229982945818f8b52f62dafddb342"}, |
| + {"internal/console/channelmove.go", "Server.executePendingRouteChannel", "record-writer", "key:UpdateRecord", "b3b9a12213261a70b680ad5290957ebe7434eff633a73c2d80c75cc24d026d9a"}, |
| + {"internal/console/claim_alias.go", "claimAliasStore.Put", "nonrecord", "def:Put,sel:Put", "a21b7cce328ebad9955211657ca8353adc7c2495b065fe09909b93c85ea7defb"}, |
| + {"internal/console/claim_alias.go", "Server.claimAliasUnbacked", "key-site", "key:RecordKey", "7edc8330b15b5dc89ca17a7b043dea53dbe2d3de9a59892b5e16aa3afe6f18e8"}, |
| + {"internal/console/claim_mail_admitter.go", "refusingStore.Put", "record-transport", "def:Put", "a66beae02acd7f15582f7b28157a36974e63877a75912591f0ea264e09a97d87"}, |
| + {"internal/console/claim_relay.go", "Server.relayPublishGated", "nonrecord", "sel:Put", "2bec06d053666dadfdda44870ffe5c03336d354f78ed0e51a23c1c812c76b1ab"}, |
| + {"internal/console/claim_retire.go", "Server.retireClaimHostRecord", "key-site", "key:claimRetireRecordKey", "5453309c94671c891e9f685aeab429ee2bd9ceaea859262d1f36b7089b8ab173"}, |
| + {"internal/console/claim_retire_helpers.go", "claimRetireRecordKey", "key-site", "key:RecordKey,key:SidecarRecordKey,key:claimRetireRecordKey", "4a84350fc30ad6866855f785e40bea690655daf6399acad853c4e61871ee82a5"}, |
| + {"internal/console/claim_store_effects.go", "claimEffectStore.Put", "record-transport", "def:Put,sel:Put", "679406564fa1f9816a4fe2fe9bd454ed6fdde1e16a3e7c5060c22fdb933e7b9e"}, |
| + {"internal/console/domainattach.go", "Server.attachDomainAs", "record-writer", "key:CreateRecord", "c99691f92daab087137bb607a89f3dc7ef4202e83431e61ebee56553756f7f4f"}, |
| + {"internal/console/domaincatchall.go", "Server.catchallListItems", "key-site", "lit:instances", "365ead1d588dfbe9397a86510c386940545bf853f77294601575f1f39c48e989"}, |
| + {"internal/console/economicspass.go", "Server.upsertBill", "nonrecord", "sel:Put", "2af1ed6992d6856f5273d5ce6305fa4b9d434f94b25c9843c5cd45a6c69362b1"}, |
| + {"internal/console/economicspass.go", "Server.putScenario", "nonrecord", "sel:Put", "80557ccbb43b60fe9e8a54a4aaf3be1e7982a92a9ba2d6e33be43ddf51329ab5"}, |
| + {"internal/console/economicspass.go", "Server.putActiveScenario", "nonrecord", "sel:Put", "0711f7092a78e391af67eca8c4a5b30224297bbddb8c7a92add8c375faef3bff"}, |
| + {"internal/console/fleets.go", "Server.placeAppTarget", "record-writer", "key:RecordsPrefix,key:SetRecordRegion", "6a17750f7774b5036e51578b23e075fb929af044304de7296c9b1f1f9e1e7118"}, |
| + {"internal/console/geoipfeed.go", "geoipFeeder.writeStatus", "nonrecord", "sel:Put", "ec634a43acb480fd1a65723bf370f7ce4e13faaf48229ddfa682efb50807ee07"}, |
| + {"internal/console/geoippublish.go", "geoipPublisher.Commit", "nonrecord", "sel:Put,sel:PutObjectFrom", "c86ed5277e20e40b0fef15badc2baa4da03625bc7d9b0456ead59fcd79257234"}, |
| + {"internal/console/geoippublish.go", "geoipPublisher.ExpireIfDue", "nonrecord", "sel:Put", "a7d4f24a25c7bda40f18268d34a99203b58aa07bc10eda0db2306b54cb08302b"}, |
| + {"internal/console/hostreports.go", "putHostReport", "nonrecord", "sel:Put", "d8ba8c77b5f0ee5bcde5c11f4a608cf2bd2491c36b8cb9bf26753a230d485ad4"}, |
| + {"internal/console/infrapass.go", "Server.putInventory", "nonrecord", "sel:Put", "c5d930ec178ee206d25194a2b265738ac42c6f00bd9b73626e4d46d769d370ad"}, |
| + {"internal/console/instancepage.go", "Server.handleAppInstance", "key-site", "lit:instances", "51073a3eb95b98982de74259fc99dd0aa375c8c1504213c7dd5ef58682daec8e"}, |
| + {"internal/console/relayops.go", "Server.relayAcquire", "nonrecord", "sel:Put", "3c757e1c92f1c0ce41c3b5f00aa1397ce5a0c4faabc2f176b23355cf5a41f5fb"}, |
| + {"internal/console/relayops.go", "Server.relayPublishOnce", "nonrecord", "sel:Put", "783a3c20337914e08f92be783f6b48ab14288437a8043c0d5bf29f281a7a9b1d"}, |
| + {"internal/console/relayops.go", "Server.relayRotate", "nonrecord", "sel:Put", "58e0090a4990e2d5e793cec2ec7bcbfb3626b4043307f64484a55766099e1434"}, |
| + {"internal/console/server.go", "NewServer", "key-site", "lit:instances", "4033fcfce048867456db39d4c293afe18b4f13e94ba756466ad786d633c8cf10"}, |
| + {"internal/console/server.go", "Server.PublishMovesConsole", "nonrecord", "sel:PutReplace", "b228397d413769f93209645db9ac6bd4a47cc9db31f06c8699cb540cffea9b5a"}, |
| + {"internal/console/server.go", "Server.handleAppInstances", "key-site", "lit:instances", "7368e00fa1bf20365b61361815db216441b7fd21b3d330349dc09170045d45b7"}, |
| + {"internal/console/server.go", "Server.handleAppActivity", "key-site", "lit:instances", "265425be83a63bf2fbcd88e1b75600933cf1a2e8b7fa9cc90046fdd329db9786"}, |
| + {"internal/dbverify/dbverify.go", "RemoveHost", "nonrecord", "sel:Put", "a96ff33a7a9e701ad101882a0f547daf31dda54e014d8d072288a48d4b95bdeb"}, |
| + {"internal/dbverify/dbverify.go", "Publish", "nonrecord", "sel:Put", "b6a7ef8e5a55e6f8e5bc15ef1c4f2f67563415d47ecfbb6abde030a514e521ee"}, |
| + {"internal/domains/claims.go", "instanceRecordsPrefix", "key-site", "key:instanceRecordsPrefix,lit:instances", "02326f5fa12f8089efb9663fa6d173f46dd6fcfdbc825486eace92620c99e593"}, |
| + {"internal/domains/claims.go", "InstanceLabelCounts", "key-site", "key:instanceRecordsPrefix", "b5f1973882f03cfebe014aae4738170801c5c9e766ebc552d5843a68007f3eb1"}, |
| + {"internal/domains/claims.go", "claimRawAttachInstanceCatchall", "nonrecord", "sel:Put", "518862e2aea95872024effbb16a621dba4ebf7314a18d5ddd7acb1671d6776e7"}, |
| + {"internal/domains/claims.go", "AttachClaimProven", "nonrecord", "sel:Put", "82ea07f38d6a9a83e9253b9b336a344bfe692ed61a6c8246450bdf739a8c5d3b"}, |
| + {"internal/domains/claims.go", "claimRawSetClaimCapabilities", "nonrecord", "sel:Put", "96dcbb0646559b45d7a7e2920fab2c770ed57c30666a7d52e18d379c50cc9fea"}, |
| + {"internal/domains/claims.go", "claimRawSetClaimPaths", "nonrecord", "sel:Put", "03506119ddd2397713b2ba2cfb0b22a986229789061fcdbf970c8b7eb052069c"}, |
| + {"internal/domains/mint.go", "MintAccount", "nonrecord", "sel:Put", "1fda85a4c1a4b09a718a96e03d65880d72cdaa2fd098ad305605eeb98a0349e7"}, |
| + {"internal/domains/mint.go", "TombstoneAccount", "nonrecord", "sel:Put", "9a400aa9e915f5df8060840701356e381bdf9be7c5af6f312cc09ab428900ecd"}, |
| + {"internal/doorkey/doorkey.go", "RecordKey", "key-site", "key:RecordKey", "a29b62f6e3a9e9f0e412dae7e477db7e7d5702afb3a92080b41bde37008d9ace"}, |
| + {"internal/edge/cache.go", "Cache.Put", "nonrecord", "def:Put", "2b42e3b7892714ba326e30250c852b46945c30ef9ed6511072f478620a5a269d"}, |
| + {"internal/edge/certsync.go", "publishingCache.Put", "nonrecord", "def:Put,sel:Put", "7bec290d7bb8ba424de938cb6c497d882c863f9d0470bee4f22b56fd292f6d57"}, |
| + {"internal/edge/certsync.go", "putCAS", "nonrecord", "sel:Put", "31e2563a764ea33230161c2c2f12b307a4f171d287b835b5027d0db98448ded9"}, |
| + {"internal/edge/certsync.go", "certMirror.reconcileLocal", "nonrecord", "sel:Put", "df50be24efba72ae51d6ca85ef7ddebc1b10509d3cc062be97af5e6573dcc1d5"}, |
| + {"internal/edge/certwarm.go", "certWarm.fail", "key-site", "key:writeRecord", "0a0b03ee3a35396dba8fe6c9a66d20bbf2140667851432e621e4ef0029f81403"}, |
| + {"internal/edge/certwarm.go", "certWarm.writeRecord", "key-site", "key:writeRecord", "0e85abae4c53c673f288898e7ae299929f4fcecc6b6abfe0d1a1bd7af6bcc5c3"}, |
| + {"internal/edge/compress.go", "gzipWriter.finish", "nonrecord", "sel:Put", "501f6118a3e983ea6b197a64dd9495afde8bbb0fb439a2bd6aab1f241fd68c20"}, |
| + {"internal/edge/edge.go", "Edge.maybeStore", "nonrecord", "sel:Put", "69802d828a2b4b1db0eb2bd5016d0c234e9ea63e85a1ec0bca8f39f8896c33bc"}, |
| + {"internal/edge/mesh.go", "mintMeshKey", "nonrecord", "sel:Put", "bfb55866660051d4c55b2a1be96e7f997c76042119a8f57472fbc41908a2dcc0"}, |
| + {"internal/edge/storedoor.go", "storeDoor.serveObject", "record-transport", "sel:Put", "95fef5f91ed7a5f07a3818b1a55baded4209e97d43d48ba21cb69be3d2c7299d"}, |
| + {"internal/edge/wildcard.go", "wildcardManager.obtain", "nonrecord", "sel:Put", "752840841afb596cba318e5c7ece964c5146a7a24edde91f9c9dc0024753dc3b"}, |
| + {"internal/edge/wildcard.go", "wildcardManager.accountKey", "nonrecord", "sel:Put", "77f7da94e302045eff8cbbb1be311bdcbaf75cc4cdd8f794d474e508fbf35a51"}, |
| + {"internal/fleetapi/fleetapi.go", "store.Put", "record-transport", "def:Put", "68f7424cbbaf8fffde48429ae29198cf6e4fcf768c512657b305224febc11186"}, |
| + {"internal/genselect/select.go", "requestLocation", "key-site", "lit:instances", "12ef5b8f94be7575ec7ab5f169736320ee6938440387fe5ea99b96f2a528cca0"}, |
| + {"internal/host/bootstrap.go", "const tmpfilesConf", "key-site", "lit:instances", "71d83f240cb1c3ab6f9dd2954b1f3e93b90133362ec12193746f60e75aeced7d"}, |
| + {"internal/host/bootstrap.go", "bootstrapWith", "key-site", "lit:instances", "ab8e1192c203cc203b5522740a086e5f6970080a23caf013c67f205addd6b470"}, |
| + {"internal/host/claimretire_transport.go", "retirementAcknowledgement", "nonrecord", "sel:Put", "5c09795fbe9b952436d16bcd58e846ac27e9dbcf959fcf1cd6c432af0f3ee416"}, |
| + {"internal/host/errship.go", "errshipFlush", "nonrecord", "sel:Put", "82831c08563eab44de07c1c69752555eafe60c70dbf9521d9a4711bc5c104a6b"}, |
| + {"internal/host/fleetbind.go", "WriteFleetBinding", "nonrecord", "sel:Put", "44ab984c2c9b30abcc938132118f61a6922ccc0cd40ed2b6cb61c7654aa0a1c3"}, |
| + {"internal/host/logship.go", "logshipFlush", "nonrecord", "sel:Put", "a2fa594c1206028af170a2d0cbedef55ba00a2f1c698a6853e1214cc3b172dd9"}, |
| + {"internal/host/metrics_loop.go", "putAlertState", "nonrecord", "sel:Put", "d5b6e8b34dc5a731d6bbafeac0b7d0c5c20706d6f413dff38088b3321b35a26c"}, |
| + {"internal/host/movesarchive.go", "cutRun.publishCutArchive", "nonrecord", "sel:CopyVersioned,sel:PutIfAbsent", "6c7fcfd2d4b2a0319ac15ecbfe003ed0292fe160457fd5704be508c2ab302a1b"}, |
| + {"internal/host/movescut.go", "cutRun.cutTransition", "record-writer", "key:SetRecordMove", "d0d67399aa784036de43b3c143e88c3d85f8b3d9e6345f84908d3b58ff56191e"}, |
| + {"internal/host/movescut.go", "publishStoppedReport", "nonrecord", "sel:Put", "c710682f31d99127ad76524add246fda9f640c63934940e45c79ea25ba36eb3c"}, |
| + {"internal/host/movescut.go", "cutRun.snapshotTimers", "nonrecord", "sel:PutIfAbsent", "9137042d3c269f8cfc94bd9d04cde24423e8824986521906c73159cd0a552ca4"}, |
| + {"internal/host/movesguard.go", "guardedMirror.CopyVersioned", "versioned-transport", "def:CopyVersioned,sel:CopyVersionedAt", "069edf385ccc13d50452bd97eae222c0312a629a48d619c922f9985aa1c99a7b"}, |
| + {"internal/host/movesguard.go", "guardedMirror.Copy", "versioned-transport", "sel:CopyVersioned", "1689745fdd642c0dbe855b12b8d1ef220f3a9c3daed7ca49bdeddfd6ff9d5ade"}, |
| + {"internal/host/movesguard.go", "guardedMirror.PutIfAbsentVersioned", "versioned-transport", "def:PutIfAbsentVersioned,sel:PutIfAbsentVersionedAt", "68457755ed3c8b470064e9ccbefe42c819f877d596352669d7126d6032a7726f"}, |
| + {"internal/host/movesguard.go", "guardedMirror.Put", "versioned-transport", "def:Put,sel:Put", "a3168524604a41ea42e8fcf6e5a1335e4ea7be77fde089aca6ad74dc64b43649"}, |
| + {"internal/host/movesland.go", "landingRun.transition", "record-writer", "key:SetRecordMove", "c943a81b6ddba9cba138710de340bc238689e0122d12953d1bb4c84e95735f8e"}, |
| + {"internal/host/moveslandpark.go", "leasedStore.CopyVersion", "versioned-transport", "def:CopyVersion,sel:CopyVersion", "a8b33c9f61f59477b76fa50472250622c717ab2883f32837c601da5eeb497e9d"}, |
| + {"internal/host/moveslandpark.go", "leasedStore.PutIfAbsent", "versioned-transport", "def:PutIfAbsent,sel:PutIfAbsent", "33542589bf2a336b181f685bd0dc5faa0285449e5ca9e850e9f4e0e7e4164c6b"}, |
| + {"internal/host/movesmeasure.go", "measureS3", "nonrecord", "sel:Put", "3ae9fc093b453e19a5b19be0929533b30a0e2a3e998ba8dc766f41287db2caca"}, |
| + {"internal/host/movespass.go", "legacyMoveRound", "record-writer", "key:SetRecordMove", "1e13a3ea099d45318d3b64b067448c87dc4a136cd909348f526e4583ac00150a"}, |
| + {"internal/host/movesphase1.go", "writeMoveLedger", "nonrecord", "sel:Put", "597a70ac06ceb4e9eb52bb9f61b28a4937e703fa4a0916f64bd95d9594f0556e"}, |
| + {"internal/host/movesphase1.go", "movesWorker.sourceRound", "nonrecord", "sel:PutIfAbsentVersioned", "c5bc2e6cb57b819ae1da485d37093c9a9ed3cdc9e9e16e8a8e312fd5ad2e2fe3"}, |
| + {"internal/host/movesphase1.go", "movesWorker.refreshCrossRate", "nonrecord", "sel:CopyVersioned", "7e03e680bd61be26dddcf168cacd2427bb535571dfd326c4a3130ebe65ea1968"}, |
| + {"internal/host/movesphase1.go", "movesWorker.writeSourceReport", "nonrecord", "sel:PutReplace", "370407cca2c1b91f6e5e7f135238f04c0dce9d7986e6e1ee9bebbf88cad0b9e0"}, |
| + {"internal/host/movesphase1.go", "movesWorker.decideReady", "record-writer", "key:SetRecordMove", "c228e6f38eca6ed10501538031d3260f016a3f26ce501d8544f1581ae7668d8f"}, |
| + {"internal/host/movesphase1.go", "movesWorker.interimAbort", "record-writer", "key:SetRecordMove", "9e875647dfdd2531c6857a4730245526c90c000986b2c9ea655bfcc4df43c02e"}, |
| + {"internal/host/movesreport.go", "movesWorker.targetReport", "nonrecord", "sel:PutReplace", "3f6aec125f1bd40271adf78833ac3268e536cbc5e8af3731b16f382669cd955b"}, |
| + {"internal/host/movesstore.go", "laneProbe", "nonrecord", "sel:PutIfAbsent", "f4a17a1143e7370b9039b2e66e736606015c46619bab92c553ce7b1ad6b7918b"}, |
| + {"internal/host/snapshot_loop.go", "registrySnapshotPass", "nonrecord", "sel:Put", "6b01b46b361c3a2d1d432659a0d234c01975849b9a8ac5571858fdd78d376849"}, |
| + {"internal/host/snapshot_loop.go", "putReplacing", "nonrecord", "sel:Put", "11e8639effce142eab707b67cbdd425d0d7cea510ddf95d292bdba8f79c9b510"}, |
| + {"internal/hostactors/censuskey.go", "Directory.BindCensusKey", "nonrecord", "sel:Put", "7d9d673435e8c8a6aac3105e3a754bde2adcbc5d603945dd599cef703f334a5b"}, |
| + {"internal/hostactors/invitation.go", "Directory.Invite", "nonrecord", "sel:Put", "356618a75a23d0157d8e0402e20187a42956f67a4332d698bb22bf05133ac489"}, |
| + {"internal/hostactors/roster.go", "Directory.Replace", "nonrecord", "sel:Put", "2f1850a8e98e1874d9b352a308316c2f613419b392280fbf2675c9552877b3d8"}, |
| + {"internal/hostbox/regionalworker.go", "RegionalWorker.Census", "key-site", "lit:instances", "5d554df4562248c6e6467f5009910dc4e5502bc09679baa55f0d2cd1bdc06037"}, |
| + {"internal/hostbox/requeststore.go", "RequestStore.CopyVersion", "versioned-transport", "def:CopyVersion,sel:CopyVersion", "3a50049ce7ca7f2cbc770bef46218944cdc7000205a86c20e4932f0b510da28f"}, |
| + {"internal/hostbox/requeststore.go", "RequestStore.PutIfAbsent", "versioned-transport", "def:PutIfAbsent,sel:PutIfAbsent", "3666cbe5d2b91c1c652ffb4459c10a374fbd92e325e4e5c574fa3525d0ce2ffe"}, |
| + {"internal/hostclaim/adopt.go", "Adopt", "key-site", "lit:instances", "24c06b99cce6c638acb19826985cfadd3c736fd0ace7289bcf7ac3dfe784f05a"}, |
| + {"internal/hostclaim/claim.go", "ParkScope.Validate", "key-site", "lit:instances", "80b798b0cb679d8c533ff562c0c4804c06f53e7dba126250a3c8182f7ff0c08c"}, |
| + {"internal/hostclaim/claim.go", "holderKeyMatches", "key-site", "lit:instances,lit:sidecars", "d15a6841ea6f8cbfa9c8407fd3af020ddeacdfa0743b9e17de412f113faf7979"}, |
| + {"internal/hostclaim/claim.go", "Claim.Validate", "key-site", "lit:instances", "e15cf2b21882fdee2aa09ceb5e2de3f86e8ef5d46d08f33f11b7ea17828a29e4"}, |
| + {"internal/hostclaim/create.go", "BeginCreate", "key-site", "lit:instances", "45ee1e2b1fc9fa8ffdef4f27cf983dccfa3acf7218dccd06f0ee6d12572f4a44"}, |
| + {"internal/hostclaim/lossrecord.go", "LossRecordKey", "key-site", "key:LossRecordKey", "1b109b49a8fcdb09f01dbf908a173394072f01afb0c9e324023fb9b3f71f98b3"}, |
| + {"internal/hostclaim/lossrecord.go", "ReadLossRecord", "key-site", "key:LossRecordKey", "1237dd3fa3e79a81e6c35f371bf545c9a04841a6297c078d96852daf25b7b483"}, |
| + {"internal/hostclaim/lossrecord.go", "CreateLossRecord", "foreign-recordkey", "key:LossRecordKey,sel:Put", "e62263731fa572ed6f195a267abb63ff5a1bf44284db3db76a819ccaec238191"}, |
| + {"internal/hostclaim/lossrecord.go", "ReplaceLossRecord", "foreign-recordkey", "key:LossRecordKey,sel:Put", "a863b85023ce2f654fcce618be11f8b21b2bbec4c7f5eaf19348e71f48cbab3e"}, |
| + {"internal/hostclaim/store.go", "Update", "nonrecord", "sel:Put", "68f97a49f9a936228cb26e49d7bed3a4ab17c3a4c4255e32c2d2af1dfccd73a6"}, |
| + {"internal/hostevidence/signature.go", "immutable", "nonrecord", "sel:Put", "54fb2bdc230c2cb4193f3b808004b63bcfd092266846fc0c03e61617c3ad0ab5"}, |
| + {"internal/hostexec/controller.go", "Controller.run", "nonrecord", "sel:Put", "03df11b375761730700ce30c0db05c57f0828629eda0a5de126ea1a2f2f7501c"}, |
| + {"internal/hostfence/termination.go", "TerminationWorker.persist", "nonrecord", "sel:Put", "3e6c7c4d1ec0310d311f890f9e2d17512791b1434e6f44f329eb750d7b2f7f27"}, |
| + {"internal/hostfiles/contract.go", "CopyManifest", "nonrecord", "sel:Put", "9754b113ca2cad5e267402a44ac25de778e332d0c87027767f9a7daa4937d9fd"}, |
| + {"internal/hostfiles/publisher.go", "Publisher.put", "nonrecord", "sel:Put", "4bf1d0c4ab995f4e5afa8d7675cbd00446da006a82a659764f8013c011f9efaa"}, |
| + {"internal/hostfiles/publisher.go", "Publisher.Commit", "nonrecord", "sel:Put", "6674cdc1514907bff41a5a94dd45be9d496a3abad52e89cc51e801520d828c2c"}, |
| + {"internal/hostguard/record.go", "writeRecord", "key-site", "key:writeRecord", "0ba1097cba3d4a5f5fc0b06765de6f8ee535befb939e83f1cc7167c7996ac60b"}, |
| + {"internal/hostops/backfill_publish.go", "Backfill.Publish", "nonrecord", "sel:Put", "7b2be2fa910195260a7c0c63a859290d4fc25e06b41a8592dc95cf2d4f41975f"}, |
| + {"internal/hostops/backfill_publish.go", "PublishCensusReport", "nonrecord", "sel:Put", "5c7ae54a82c3cf47d166c3b02123b60a9aff06e52b500a34d84cd668f4e29b36"}, |
| + {"internal/hostops/control_writer.go", "ControlWriter.updateAdmission", "nonrecord", "sel:Put", "b8777d9f07582d452e6d12a4e2f417e9c1804f1b87d32b13cb7172c515854cf5"}, |
| + {"internal/hostops/create_effects.go", "ProductionAuthority.RecordEffects", "record-writer", "key:WriteRecord,sel:Put", "21550e2b7d739956fcfad7b9ad7aaaf7a3f8c98948de8417ad9fd557f666251f"}, |
| + {"internal/hostops/enroll.go", "ControlWriter.EnrollNewApp", "nonrecord", "sel:Put", "93cfb1ab60a67dcfa13bf674736284edfb60076fcf5a22d6eefae2c31ce50275"}, |
| + {"internal/hostops/enroll.go", "ControlWriter.EnrollBackfillApp", "nonrecord", "sel:Put", "db2de4e780333bb4c2507e50ef81be2bb87467b2ea38ae7599fa283eedcb056b"}, |
| + {"internal/hostops/geoip_classifier.go", "classifyGeoip", "reference", "str:Put", "f752cc89ab5c507fe5681bfce9fd99d8870b83af5c81ec30ddcaf70847ad02f5"}, |
| + {"internal/hostops/lifecycle_grant.go", "authorizeSignedControl", "reference", "str:Put", "cf012aebc7b4e0ea8dfef48ea3817a7466f1dabacd7cf1e5be07b2302bf183ba"}, |
| + {"internal/hostops/lifecycle_record.go", "type lifecycleRecordKey", "key-site", "key:lifecycleRecordKey", "09ab1fd62d0be032bd735d1f60488cdb19128f8573b00bbd7a70f582208da75d"}, |
| + {"internal/hostops/lifecycle_record.go", "setLifecycleRecordMove", "record-writer", "key:SetRecordMove,key:lifecycleRecordKey", "37691dfd5158c644b12e28383243f92a7868e075eade79be79e7149a18f8d420"}, |
| + {"internal/hostops/lifecycle_record.go", "conditionalRecordStore.putLifecycle", "record-writer", "key:putSequencedRecord", "03ac054bfed5f28429eaaefca22ab940ad71bace1897064e1c770cec361bcc03"}, |
| + {"internal/hostops/mail_gate.go", "ControlWriter.AcquireMailGate", "nonrecord", "sel:Put", "f37147b214f176da0565268aba714f15216f3bdd9c5f279d6c404098a499b482"}, |
| + {"internal/hostops/mail_gate.go", "ControlWriter.ReleaseMailGate", "nonrecord", "sel:Put", "aea8bc583a0a28cd61e8c81c6d466b02e4fb6a086ba921f01fdd7dfdb286191c"}, |
| + {"internal/hostops/mail_gate.go", "ControlWriter.RecordMailGateMember", "nonrecord", "sel:Put", "ed87514c0cfd15664765deb5aa0051c37b14ecebf0ba1a77459da4f8b7e7465f"}, |
| + {"internal/hostops/mail_reconcile.go", "ControlWriter.ReconcileMailGate", "nonrecord", "sel:Put", "6f9d68daf39704885ada14b7dc9f3e8e5a0424455489aed45fc7b166bdb8e726"}, |
| + {"internal/hostops/move_start_resolve.go", "moveStartRecorder.Put", "record-transport", "def:Put,sel:Put", "33b119350809eea087f21e124f541549c63b4bd51cb44d5e04295ac55e1e3f65"}, |
| + {"internal/hostops/move_start_resolve.go", "decideMoveStart", "record-writer", "key:WriteRecord", "11a22d2cfd9cc4efb6afc3417e034fa881c1ff1491fba11a4dd04dfb3d9dcdea"}, |
| + {"internal/hostops/mutation_boundary.go", "mutationBoundary.Put", "record-transport", "def:Put,sel:Put,str:Put", "c4222199cdbfa8cce1f97f91bf41fe94857f4d833e30b17636c40f1aae39ff67"}, |
| + {"internal/hostops/mutation_boundary_putfrom.go", "mutationBoundary.PutFrom", "record-transport", "def:PutFrom,sel:Put,sel:PutObjectFrom,str:Put", "8d015a924901f0c41e142181f986cb9d2a3bdfa6a9e078dfda05a2d1e7cf9b24"}, |
| + {"internal/hostops/mutation_boundary_putfrom.go", "conditionalRecordStore.PutFrom", "record-transport", "def:PutFrom,sel:Put,sel:PutObjectFrom", "3c3cb33a601eab5c4d74042a7ca8baf411c0549508b5a8273ef88f4ee0e9636c"}, |
| + {"internal/hostops/mutation_classifier.go", "lifecycleFamily", "reference", "str:Put", "75df2a3966d0bfe4b4b367617de3ebe28055a38ef4738f352859ba94ddbff241"}, |
| + {"internal/hostops/mutation_classifier.go", "ClassifyMutation", "reference", "str:Put", "d5a8784a775e862aaa093c9fb7d6a2c479c181f2a88baf79abef223732925861"}, |
| + {"internal/hostops/mutation_classifier.go", "ownerUnion", "reference", "str:Put", "0c35233653dfe9ca6cc78bc54bac2c6991f82c9a7c2fd27c228c125f099f489b"}, |
| + {"internal/hostops/mutation_classifier.go", "classifyRelayAuthority", "reference", "str:Put", "2887dce5f9f73b637c3a66308fe315f3ee20369263aa462b644d85048cdffc08"}, |
| + {"internal/hostops/mutation_classifier.go", "classifyRelayIssuer", "reference", "str:Put", "106e4975e12208e561ac4a021a24db9390ac0ad6e40e76d68886be3275335cc1"}, |
| + {"internal/hostops/operations.go", "Controller.Attempt", "record-writer", "key:WriteRecord", "f55e641a27d2f42350dc12cc0e0d5361712bfed61a76b73877beca527a833411"}, |
| + {"internal/hostops/purge.go", "ReservePurge", "nonrecord", "sel:Put", "176d5e62d9410122043d2c5e534bda4dfc66b438226c54482389f5e5b0bf8976"}, |
| + {"internal/hostops/purge.go", "AdvancePurge", "nonrecord", "sel:Put", "f0fc6dabadaa5cc6a34c8656f3a6ca04ead8bc56d0bca1e539b0cde937d23c17"}, |
| + {"internal/hostops/purge_control.go", "ControlWriter.ReservePurge", "nonrecord", "sel:Put", "902ee70445e47674aa91e80a377346dfef46ad25451a885acf69a6c017ba11a0"}, |
| + {"internal/hostops/purge_control.go", "ControlWriter.SavePurgeJournal", "nonrecord", "sel:Put", "23e1168f819b7d5ae14bebee1c8b13c73b3d2a66bad7652f22096bb5557779e6"}, |
| + {"internal/hostops/purge_journal.go", "PreparePurgeSweep", "key-site", "lit:instances,lit:sidecars", "e267dc12d744cdb041e94676c7420d8183a4924c03f87e5d1e5061366f3e70d0"}, |
| + {"internal/hostops/record.go", "ReadRecord", "key-site", "key:RecordKey,key:SidecarRecordKey,lit:instances,lit:sidecars", "312a2accb2ccb01f1f55a63ad6b97362e234959a0db07593ae5d37c0448e6d4e"}, |
| + {"internal/hostops/record.go", "Records", "key-site", "key:RecordsPrefix,key:SidecarsPrefix", "7c0055d4e5e2ee8b269b0016f4637dad1bb93bac10273403cfb4f20ef567dfd0"}, |
| + {"internal/hostops/record.go", "PrepareInstance", "key-site", "key:RecordKey", "ba2f91ad03e8e7dbe61c4ea4be41fdfb117e024f4ff98ba528214de368737105"}, |
| + {"internal/hostops/record.go", "PrepareSidecar", "key-site", "key:SidecarRecordKey", "87f836e68baef64540fa76b4b53f7e02c09811bb9fc1e88ac8316fcf71a50593"}, |
| + {"internal/hostops/record_sequence.go", "putSequencedRecord", "record-writer", "key:WriteRecord,key:putSequencedRecord", "918911815b9b686ddce3ac72244f0e6c2c0167aeb2b81ee8e72d7b5f12fc407b"}, |
| + {"internal/hostops/record_writes.go", "provisioningKey", "key-site", "key:IsRecordKey", "02956434b2d58bf6b7803eaef1355e31e7bf5ae9b46dcf21b5150d03ce018f1a"}, |
| + {"internal/hostops/record_writes.go", "conditionalRecordStore.Put", "record-transport", "def:Put,key:lifecycleRecordKey,key:putSequencedRecord,sel:Put", "ac3b57df2e57d691d6d8c5456ca02b303e93aab43ce87e544e255f65586f7436"}, |
| + {"internal/hostops/retirement.go", "Controller.RetireRecord", "key-site", "key:RecordKey,key:SidecarRecordKey", "6baf2d85f03536957f39b576dfcd0b24101da0fed7846dd15d56355d2198aa73"}, |
| + {"internal/hostops/sidecarwake.go", "ProductionAuthority.TouchSidecarWake", "key-site", "key:SidecarRecordKey", "c6fb9c19d0c3097cdb879133ac93e681e5b2d0ac37cc829695bdc25f1ff3f2dc"}, |
| + {"internal/hostops/storage_classifier.go", "classifyDeploymentFamily", "reference", "str:Put", "9b690dd8ee599fdb337615eb9ca56274b5b19c68c9a0df743c4023d06a16af16"}, |
| + {"internal/hostops/versioned_boundary.go", "versionedBoundary.classify", "key-site", "lit:instances", "6edc0d81310fa92b273e2ea98903d53d1adcde53df01c1ca9e8c89f0cc51fc66"}, |
| + {"internal/hostops/versioned_boundary.go", "versionedBoundary.CopyVersion", "versioned-transport", "def:CopyVersion,sel:CopyVersion,str:CopyVersion", "63f158fd4372fb58b67f24bb99ce6fb286b0818c4ee2a762e452ec9a4b9d2d1e"}, |
| + {"internal/hostops/versioned_boundary.go", "versionedBoundary.PutIfAbsent", "versioned-transport", "def:PutIfAbsent,sel:PutIfAbsent,str:Put", "63e387d09247046d9886e11150b92f86c27a79cdb7b7eb06542bada68ea9afd9"}, |
| + {"internal/hostresolve/service.go", "acceptanceKey", "key-site", "key:LossRecordKey", "64b38b14a779519167ccb041705cb17daaeaad11944975bbc28de66b65549a93"}, |
| + {"internal/instanceexec/exec.go", "execLaunch", "key-site", "lit:instances", "43f666a31cb035bb4c657c2366a6c9d95eb7c87020af08b20cb5e54cab227d79"}, |
| + {"internal/instanceexec/exit.go", "RunExit", "key-site", "lit:instances", "8769b8529df484503286391fa1623bd4a554d08833738d01faea163f14e2a9e5"}, |
| + {"internal/instanceexec/exit.go", "writeExitRecord", "key-site", "lit:instances", "0a67f3a6b9d8a5fd82d62b466368ee88d7a4a63f4c7446752b5b8f5237357a9b"}, |
| + {"internal/instanceexec/probe.go", "runChecks", "key-site", "lit:instances", "da0d0ab869742d938ed6474ad84a2ae1283935b66402261e3338ce6644e39116"}, |
| + {"internal/layout/plan.go", "nestedFiles", "key-site", "lit:instances", "f7add773fc8440c54708c49edd9507dd1ef8344605d841e6614ca344d392b974"}, |
| + {"internal/layout/plan.go", "PlanFor", "key-site", "lit:instances", "cf4461a2970d4c75fc9e2b4a2164a2ad0a64753ec89d6bb580848550eec96451"}, |
| + {"internal/lease/mirror.go", "type MirrorStore", "reference", "id:Put", "aa6ae137b4c418d6b67f8f81ed9370c0b1d6571b502bf7cd2f92cb3fe907bb0f"}, |
| + {"internal/lease/mirror.go", "type VersionedCopier", "reference", "id:CopyVersioned", "f5a89ac86e0dd0166a541e4ea233159363d2de3ed300e19ece59a9295b7e2835"}, |
| + {"internal/lease/mirror.go", "MirrorPrefixKeep", "versioned-transport", "sel:CopyVersioned", "8a58fdbc7ff769ee235834dc3dba09271cfd52024778189ee1cd2bc957cc5541"}, |
| + {"internal/lease/mirror.go", "receiptLedger.save", "versioned-transport", "sel:Put", "9ccc7f0b3f6b836de3cd4e5d609130126ce3ae9a4b1dcfb94a19dae33d81656f"}, |
| + {"internal/lease/mirror.go", "s3Mirror.Put", "versioned-transport", "def:Put", "c57f634a2acd76e578e92b60aae22f89aff38fe3c74a99f92ca80fe94ce4de50"}, |
| + {"internal/lease/staging.go", "type StagingWriter", "reference", "id:CopyVersionedAt,id:PutIfAbsentVersionedAt", "69da8042e79db6c0aff7c0f80b3ca45f1308ac6484124da15ec9afb56bfa6c01"}, |
| + {"internal/lease/staging.go", "StagingMirror.CopyVersionedAt", "versioned-transport", "def:CopyVersionedAt", "57c4bf86ffd632cb92d8583afaf011987df415927c6959cb9eba651cd9242c78"}, |
| + {"internal/lease/staging.go", "StagingMirror.PutIfAbsentVersionedAt", "versioned-transport", "def:PutIfAbsentVersionedAt", "8d2c41d739e4dba9500c6024f0a280e8d1d95f847739206e8b73c370517502f4"}, |
| + {"internal/lease/versioned.go", "type VersionedStore", "reference", "id:CopyVersion,id:PutIfAbsent", "b66751605e2096748dbe25db1711c3398d499f85c8401404af97e8576acca634"}, |
| + {"internal/lease/versioned.go", "versionedS3.CopyVersion", "versioned-transport", "def:CopyVersion", "69411c6bd1aa320f1b63a2055a011bfb6731e2fab5de6dab9ac5569c82681b73"}, |
| + {"internal/lease/versioned.go", "versionedS3.PutIfAbsent", "versioned-transport", "def:PutIfAbsent", "ee832e720c79bd3e2df7318701fe8138215a41a3836b9fcbd628aad8a083295d"}, |
| + {"internal/lease/leasetest/bucket.go", "VersionedBucket.Put", "testsupport", "def:Put", "3c43a27aa85614e017e3594651c9a96378cfbaab7eb9c9547fb8888586c20f10"}, |
| + {"internal/lease/leasetest/bucket.go", "VersionedBucket.CopyVersion", "testsupport", "def:CopyVersion", "db31daeb0a938d214e42bbeb2377a03e856a05b589beb53d1db3f349b3e0ac96"}, |
| + {"internal/lease/leasetest/bucket.go", "VersionedBucket.PutIfAbsent", "testsupport", "def:PutIfAbsent", "bc77cff80b5a36ffadff0a3ee058daa14d0041672c10f845deaf9a729a1061cb"}, |
| + {"internal/lease/leasetest/contract.go", "RunVersionedStoreContract", "testsupport", "sel:CopyVersion,sel:Put,sel:PutIfAbsent", "4dd3215813c8136f197b2abe1dbca0372930494ed7582bea9c5470cfc97e2064"}, |
| + {"internal/ledger/append.go", "claimRawAppend", "nonrecord", "sel:Put", "df8964d0eaa3c10dfb67a8f45014482f27c19b36c618b5cbe6152e7cca73a54a"}, |
| + {"internal/ledger/blob.go", "claimRawPutBlob", "nonrecord", "sel:Put", "13ab752b369f7d5337bed20f49b968d2bdf962e50c2f2b41b82b7de1eed35f42"}, |
| + {"internal/ledger/manifest.go", "claimRawUpdateManifest", "nonrecord", "sel:Put", "0555b5c27dcbdab706296715a547e6616474bc476deeb90d81d6a1b6a204535b"}, |
| + {"internal/mail/claim_gate.go", "ClaimMailStore.Put", "record-transport", "def:Put,sel:Put,str:Put", "3a65d4aa9beae7b04c9cecfc535cca721a6e8b0b29aacae5705a0e8e6ff9528f"}, |
| + {"internal/mail/domain.go", "claimUnserializedPutDomain", "nonrecord", "sel:Put", "28c54e01762f183c6acd300bbf90eaa8147908f57e7fdd7061765bbb0fd37250"}, |
| + {"internal/mail/domainclaim.go", "claimDomain", "nonrecord", "sel:Put", "1a243e3ddeaa91bfe9adf18a8063ab57246f8eba99c5ff47f0baa0169a97cf3e"}, |
| + {"internal/mail/domainclaim.go", "updateOwnClaim", "nonrecord", "sel:Put", "a2f77a02681ea901d8b64ba79112fc1e1a37dd9da2d990b8e72785dda5bf46cf"}, |
| + {"internal/mail/domainclaim.go", "takeLapsedClaim", "nonrecord", "sel:Put", "5ca88f3eaa637fe205ea40805d481279e9ca193ea0a8067a2c4b612ed7e275b9"}, |
| + {"internal/mail/mail.go", "claimUnserializedPutSender", "nonrecord", "sel:Put", "17f674a7ccb2399cf99fbf00b55dd4cf7509c0860f7c11ecfc2abb87264b85b1"}, |
| + {"internal/mail/mail.go", "Put", "nonrecord", "def:Put", "16b0ddaddbbd7b91eab8d251835753f765e276da3bf1306325471a3ccf5f2ac3"}, |
| + {"internal/mail/sender.go", "claimUnserializedPutCallerIndex", "nonrecord", "sel:Put", "e6d3db589e23430412bcee048153739c2f6abcace2cf8995327a797e1b211457"}, |
| + {"internal/mail/walk.go", "claimUnserializedDeclare", "reference", "id:Put", "b2a49ced130ea3d9ccb75bbfc6efc527ed449a4ceeaaaf8f1678f916892d1889"}, |
| + {"internal/mail/walk.go", "claimUnserializedEnable", "reference", "id:Put", "f02b268deb95f1949f3d18607f2ab4dc2e97044ca696c00a37581230ec301afe"}, |
| + {"internal/mail/walk.go", "claimUnserializedApplyGrant", "reference", "id:Put", "63b56194ecd00f6710871055ed80298f0cf2eab5f8afd1e2567bd1b11cdbec1c"}, |
| + {"internal/mail/walk.go", "claimUnserializedRotate", "reference", "id:Put", "00f51a805d0ae7ace39d0af0e731be356ff2ad4e069a00e834333ee9bfe4ab1d"}, |
| + {"internal/mail/walk.go", "claimUnserializedRotateFinish", "reference", "id:Put", "9a576cb8d6757779f66dade82ebe250bf04f70fcd7f89daa444b68c053cbcc98"}, |
| + {"internal/mail/walk.go", "claimUnserializedCreateStandalone", "reference", "id:Put", "57e6a71b0f90ad900064c25d50e07315ee8e473bcb3a3199f578c1fa04ff522a"}, |
| + {"internal/mail/walk.go", "claimUnserializedRevokeStandalone", "reference", "id:Put", "1559a16e0af4187c69afe47b9c957632198e477e2e031ae40aa7e8c98e5c9fcb"}, |
| + {"internal/maintenanceguard/guard.go", "Repository.writeGuard", "nonrecord", "sel:Put", "f7fb0e390276f12912568a2cc120f4a214c949949064471a7d499471e7b5877d"}, |
| + {"internal/maintenanceguard/lifecycle.go", "Repository.AcquireClaim", "nonrecord", "sel:Put", "854d783bc1290fdce4cd5cc2c900496c7e950263cd55a786021b231753d3d6c3"}, |
| + {"internal/maintenanceguard/lifecycle.go", "Repository.ReleaseClaim", "nonrecord", "sel:Put", "5fcfb67c5693e9162b818e03b286de78d9d5950ebb44a1be6497f2190dc3b755"}, |
| + {"internal/maintenanceguard/store.go", "type Store", "reference", "id:Put", "f824e9c20bb778bac2cc5f69d4d7417b3e0eb600630e3f19712de2292508ffaa"}, |
| + {"internal/maintenanceguard/store.go", "Repository.putImmutable", "nonrecord", "sel:Put", "f2c831e0dd53ca7731ae32f21783a3b64ad7bd85d75a7b70a190e82c3d6e75a2"}, |
| + {"internal/metrics/chunk.go", "WriteRaw", "nonrecord", "sel:Put", "157a416dc7c3d3b2d69a902a7585777ace4adaba1e9cbd75b7a8b1163f6b2149"}, |
| + {"internal/metrics/rollup.go", "UpsertRollup", "nonrecord", "sel:Put", "4a72d2a8b68ba66f8efbdf66bbe08c9f4b43b0fae451e2b8a20ba1259c8e52eb"}, |
| + {"internal/moves/build.go", "putOrReuse", "nonrecord", "sel:PutIfAbsent", "932e5852c1d7c747db1013954352bbeb00364329d3515ae723a144b074567562"}, |
| + {"internal/moves/build.go", "putOrReadIntent", "nonrecord", "sel:PutIfAbsent", "d53112f8f9c0c48844f930d2b9ae5e58b75588ef31b0a3740b65a61c83fba613"}, |
| + {"internal/moves/build.go", "putOrReadManifest", "nonrecord", "sel:PutIfAbsent", "3187b04355f4ec75b86edf311a732c6cfecd722322b509f0731479f738b52a85"}, |
| + {"internal/moves/readiness.go", "PutReplace", "nonrecord", "def:PutReplace,sel:Put", "3ec4d72cb252b34a402175cb7866dee4ab4f5b2441958f9fc455f687ffb2eafd"}, |
| + {"internal/objstore/entitlement.go", "PutEntitlement", "nonrecord", "sel:Put", "35b3734ff8587089c913ba7151f17955e9b1f7456a2f5ae1b3fb0e6917104005"}, |
| + {"internal/objstore/memscanner.go", "MemScanner.Put", "nonrecord", "def:Put", "bef5afd48d145dafdcbda197b4ad221766833d1c8ba8223f81df98495c860a4b"}, |
| + {"internal/objstore/nameclaim.go", "claimName", "nonrecord", "sel:Put", "4a83752eec31d8b15612f42d22d80cc54bfd46239a884acb4fe7b56fa1839df6"}, |
| + {"internal/objstore/objstore.go", "claimRawPutIn", "nonrecord", "sel:Put", "19e8f49ac1ffa115c126c31583257bc5fd4ec71e84c0eba54fbc7a27a7fc9d3b"}, |
| + {"internal/objstore/objstore.go", "Put", "nonrecord", "def:Put", "a1b8b7a0ec9e1d98ab47d64341206fced4b17ac98c36b16c89337a4f5cde2ef9"}, |
| + {"internal/objstore/queue.go", "claimRawEnqueue", "nonrecord", "sel:Put", "40b1e545727ca230206aa3c7c264c5e7efb9f1772c68318c6655a8f926a90386"}, |
| + {"internal/objstore/scanresult.go", "claimRawWriteScanResult", "nonrecord", "sel:Put", "4be5c156346db152607a2c6437081c2e28e4ef600b097687c9cf0f2e22c85665"}, |
| + {"internal/objstore/sidecar.go", "claimRawPutSidecar", "nonrecord", "sel:Put", "233e3d808dd462ff83aecdac5176b180556b948c552aa26f6e0b778df91a24c7"}, |
| + {"internal/ops/litestream.go", "FleetDBsWithSkipped", "key-site", "lit:instances", "f62291df0685db15529c4946dca358a29aba66fc31e81aab299de579e37f60f1"}, |
| + {"internal/park/remote.go", "locationOf", "key-site", "lit:instances", "83b3efca31b3faadcd5997dc181d81b7bb8fc866cc4a448683bf8423b1bea866"}, |
| + {"internal/park/remote.go", "Engine.Run", "versioned-transport", "sel:CopyVersion,sel:PutIfAbsent", "dd4b60ef9b3f5e186b9866cee818f2684ed93bd4e1c934cd66d1af9ef376d81e"}, |
| + {"internal/park/sweep.go", "Decide", "key-site", "lit:instances", "c4ec278026ad3b99ed088471bf32a2b2787b19b948587b34f784e97a31a29f1d"}, |
| + {"internal/park/sweep.go", "Engine.Discover", "key-site", "lit:instances", "69b83e6e859c1c9809ba76d46cb0192888df1ecdcd161b4f2b6435733eecdfa3"}, |
| + {"internal/provision/claim_app_writes.go", "UpdateRecord", "record-writer", "key:UpdateRecord,key:claimRawUpdateRecord", "caf714cb78e1280d489c7ad5b3cefb1420e812d541108b1fe13d733abed97229"}, |
| + {"internal/provision/provision.go", "ManifestKey", "key-site", "lit:instances", "b8ee0e77bdfade19af79a4012c411c1eb0b6ddca16037e5536fa8597f7cde270"}, |
| + {"internal/provision/provision.go", "RecordsPrefix", "key-site", "key:RecordsPrefix,lit:instances", "19f1df727272f7d53a1c2250e31a73efc57087a548ea0ff2da0a7a21f3f7d3dd"}, |
| + {"internal/provision/provision.go", "RecordKey", "key-site", "key:RecordKey,key:RecordsPrefix", "9e87f9f399f33b184d4ec9387733d8465d5de3a30f46576216a6ba60f36e845e"}, |
| + {"internal/provision/provision.go", "ParseRecordKey", "key-site", "key:ParseRecordKey,lit:instances", "ab1740221c0c4ab0390b4273f92dd98fa170aaf0513d6c0b56fc7fe38ca935b3"}, |
| + {"internal/provision/provision.go", "claimRawPutManifest", "nonrecord", "sel:Put", "cb88150470d6fe2a6273d37f0f65af54690c38b60c23f8ef79c4dc5e3ff1edf7"}, |
| + {"internal/provision/provision.go", "claimRawPutManifestIfMatch", "nonrecord", "sel:Put", "66922918fe679fa9facbabe954aeefc49a2c4e10af9cf2a630e04d8fb33fc325"}, |
| + {"internal/provision/provision.go", "CreateRecord", "record-writer", "key:CreateRecord,key:RecordKey,key:WriteRecord", "029e9dd2bfb9ae39911ba556159193a8cc3b8bc3dbba19431295ce06677c6911"}, |
| + {"internal/provision/provision.go", "SetRecordRegion", "record-writer", "key:SetRecordRegion,key:updateRecord", "fc222730ce2bbb99e1ef95621325f2d6d77c78a08b042e6a97f423d95b0e56fb"}, |
| + {"internal/provision/provision.go", "updateRecord", "record-writer", "key:RecordKey,key:WriteRecord,key:updateRecord", "dbf350aebca87b7e27330e8ce941406f67d0cd70afdc3638864c5ae41889549b"}, |
| + {"internal/provision/provision.go", "SetRecordMove", "record-writer", "key:SetRecordMove,key:updateRecord", "4823514845c8f036d37db3fbefdfe4a93a7f62b9ca3c0728a571dd9db1da4f9e"}, |
| + {"internal/provision/provision.go", "DeleteRecord", "key-site", "key:RecordKey", "67e8f77d784d3e84626f2856bd45d219759ba4b603bc3daebbb9e315131c4945"}, |
| + {"internal/provision/provision.go", "claimRawUpdateRecord", "record-writer", "key:RecordKey,key:WriteRecord,key:claimRawUpdateRecord", "26adfc4074be815031c8aee929914f60537de925a6d1aba6737772b149b8987c"}, |
| + {"internal/provision/provision.go", "GetRecordETag", "key-site", "key:RecordKey", "d9f71e5a52091dce7ea6b6aa3e5d725456d5944efa180727674e7df0006d4fa5"}, |
| + {"internal/provision/provision.go", "ListRecords", "key-site", "key:RecordsPrefix", "d2cc5fb016d58b8091277545187a96c184e178ea5677e72890dae0fef2d68e93"}, |
| + {"internal/provision/provision.go", "InstanceDir", "key-site", "lit:instances", "4defc7f996c83afe8d2d2efddd31f4de79d94a5c84e7ee99e8d639ca03d7bf81"}, |
| + {"internal/provision/provision.go", "InstanceRunDir", "key-site", "lit:instances", "c0e58b35ea6c773c71524a7d9f5ab22c9a7bec46d0e0ab230784b935f77186c9"}, |
| + {"internal/provision/provision.go", "NestedPaths", "key-site", "lit:instances", "64a91261fe43412aab90175f5af2333c35a5fef7232ce20c64fd678a27130826"}, |
| + {"internal/provision/reconcile.go", "Reconcile", "key-site", "key:ParseRecordKey,key:RecordKey,lit:instances", "5d066d1045533a400b2d1c70e919507c2311cf7b633810bfbe67b3f6ce535180"}, |
| + {"internal/provision/reconcile.go", "mintHeld", "key-site", "key:RecordKey", "71e106094dc5e7aa28c82c94d41a735a1599d95e10ac422528bdf01c2a13ba3b"}, |
| + {"internal/provision/reconcile.go", "ListAllRecords", "key-site", "key:ParseRecordKey,lit:instances", "2396d277161244603476c6700c71d7bf6d89e0f8ccfd8eae12f8a05fa446e75d"}, |
| + {"internal/provision/record_sequence.go", "IsRecordKey", "key-site", "key:IsRecordKey,lit:instances,lit:sidecars", "71ee3cc5e6c1399befa39612262a7a6656f2464c760419c1a87438d8c600b6cb"}, |
| + {"internal/provision/record_sequence.go", "type preparedRecordKey", "key-site", "key:preparedRecordKey", "76c4a83eb76398bbe14e31fe82f9999b863e030082c240389bcd990e2d0475dd"}, |
| + {"internal/provision/record_sequence.go", "WriteRecord", "record-primitive", "key:IsRecordKey,key:WriteRecord,key:preparedRecordKey,sel:Put", "8632f3fa656c3cbda8d7aafebaf193be4d810f43de3dc6be39c30584169949b2"}, |
| + {"internal/provision/replica.go", "CreateRecordReplica", "record-writer", "key:CreateRecord", "84c162fe2559edb54ceae44e80e0a2e944780fb1953289c66f07658adb829fbe"}, |
| + {"internal/provision/sidecar.go", "SidecarManifestKey", "key-site", "key:SidecarManifestKey,lit:sidecars", "966e3d9f62dda09061eb2d99484074478ed04b7aa9174f33e09c9250dc3482ed"}, |
| + {"internal/provision/sidecar.go", "SidecarsPrefix", "key-site", "key:SidecarsPrefix,lit:sidecars", "da2d221da0c8c6da9f4900105fa29879c89e85c42cbdc0646637d5d6a1be06bf"}, |
| + {"internal/provision/sidecar.go", "SidecarRecordKey", "key-site", "key:SidecarRecordKey,key:SidecarsPrefix", "c42823b31edb5b69e177645c497abaa5b14176e58c3d4be3930185d0e6568a17"}, |
| + {"internal/provision/sidecar.go", "ParseSidecarKey", "key-site", "key:ParseSidecarKey,lit:sidecars", "bcfcb2f6ef70e8c8f9f4ab8f19824659d45b030b2152c4b1682561a91cabf7a2"}, |
| + {"internal/provision/sidecar.go", "GetSidecarManifestETag", "key-site", "key:SidecarManifestKey", "39cd7c3c05318ab94d75b808aef957fe79d0ec724f1db54f2a3f60cb0e149a75"}, |
| + {"internal/provision/sidecar.go", "claimRawPutSidecarManifest", "nonrecord", "key:SidecarManifestKey,sel:Put", "66401ceb2d42a4fe11bc99441adc129db9698ebe767a6349dc11ac9c0e1ccc3d"}, |
| + {"internal/provision/sidecar.go", "claimRawPutSidecarManifestIfMatch", "nonrecord", "key:SidecarManifestKey,sel:Put", "89038c29241c04ab629e0fe90af058bc38a5cce3e3a73d1466293c69c620b9e8"}, |
| + {"internal/provision/sidecar.go", "CreateSidecarRecord", "record-writer", "key:CreateSidecarRecord,key:SidecarRecordKey,key:WriteRecord", "93de6854447d263138da0b47c325dd608a2bce699f00c5de1367c323528158c8"}, |
| + {"internal/provision/sidecar.go", "GetSidecarRecord", "key-site", "key:SidecarRecordKey", "0acb1da9d9e825e8ae17c50535ca62f606ece6e4bcffd410ec57319125947481"}, |
| + {"internal/provision/sidecar.go", "ListSidecarRecords", "key-site", "key:SidecarsPrefix", "1e9fcdf53f04a9fd9ffb35574f7f63e778307d0deda8f34150b36047443fddf0"}, |
| + {"internal/provision/sidecar.go", "DeleteSidecarRecord", "key-site", "key:SidecarRecordKey", "b4dc694473fdf9e9787e15a11bf701a24e6bc32f0bf83245ad949ec7408b81e6"}, |
| + {"internal/provision/sidecar_reconcile.go", "ReconcileSidecars", "key-site", "key:ParseSidecarKey,key:SidecarRecordKey,lit:sidecars", "f9afc65bd1fb5dcba13fc6fa386b6decadcba2c509c9020acb36bab7fc1b6da2"}, |
| + {"internal/relay/authority.go", "WriteHeartbeat", "nonrecord", "sel:Put", "286ea24e50f836f6d2857b249a3013b97e56e3755104acc0cffc7ac175be98cf"}, |
| + {"internal/relay/meter.go", "Meter.Flush", "nonrecord", "sel:Put", "a0bd6d6ddd67428aca3ad320985e4000d234743a241ac2811d0b8de32604923a"}, |
| + {"internal/relay/record.go", "RecordKey", "key-site", "key:RecordKey", "420c67cb7259661f04a834334779ab9aff2ff8bf7079d6dd2bdee09eb3dcefce"}, |
| + {"internal/relay/record.go", "GetRecord", "key-site", "key:RecordKey", "52bb878edc70d064148c98afc8e6a0585a7ec02b7fd3e058374894c2a9481e92"}, |
| + {"internal/relay/record.go", "PutRecord", "foreign-recordkey", "key:RecordKey,sel:Put", "120948d4a8b9a5c40b7b497b178bc72e02b472bdfbacd0da11fa05601d1e28c1"}, |
| + {"internal/release/claim_purge_request.go", "RequestClaimAutoPurge", "nonrecord", "sel:Put", "767be49513ff9be09dd01848b7b71ed3e7470e38ebf333bcf897ad1efdd309f5"}, |
| + {"internal/release/features.go", "claimRawUpdateFeatures", "nonrecord", "sel:Put", "366e6cbd3035e22ac92289493f10c664b80350dbe353b405fa2e725900034864"}, |
| + {"internal/release/layoutintent.go", "AppendLayoutIntent", "nonrecord", "sel:Put", "3b97c12b334c897d3d2133ae4785f89c299a129714ebf5a1d069df274c59df32"}, |
| + {"internal/release/pipeline.go", "claimRawSetPipeline", "nonrecord", "sel:Put", "760debafca52df092e9b318e05d38945ef91d3313923c39f4e2c0664dbfda798"}, |
| + {"internal/release/production.go", "claimRawSetProduction", "nonrecord", "sel:Put", "0dd1875a9fcf58da306e8f1a009291b370650b6deeb5fda92afa89f979866ec2"}, |
| + {"internal/release/proof.go", "PutProofPending", "nonrecord", "sel:Put", "cea0ebb0d4f6a3ec44df9aec22c9f27eb4440786d5135b6d942059208768016f"}, |
| + {"internal/release/release.go", "putImmutable", "nonrecord", "sel:Put", "c1fb19059ffc1a2b5f5c5031b987b86b6a5e7e63a925a3e28e260eb758f60d0e"}, |
| + {"internal/release/release.go", "Signer.claimRawPromote", "nonrecord", "sel:Put", "8899b2b0eefc437788b59153110d633d329fb6346d2a2e6292927fb3168a1b00"}, |
| + {"internal/release/release.go", "Signer.claimRawRollbackAuthorized", "nonrecord", "sel:Put", "e98aea56c4324bfbddd3a7e70a5727a6181eb691ce81ee52b4d9ea30f82c0ea2"}, |
| + {"internal/release/release.go", "claimRawFastTrack", "nonrecord", "sel:Put", "880f3f49a5979d8f7f9feae7cb5b8646a46533ed4ca8c64ec53cda8cab5b50e4"}, |
| + {"internal/release/restartgen.go", "claimRawTouchRestartGen", "nonrecord", "sel:Put", "4a2797646f540e3d02798e7c5f427afa582986ea9a0818492f90a58f684a5ce0"}, |
| + {"internal/release/restartscope.go", "claimRawAppendRestartScope", "nonrecord", "sel:Put", "c1d51642d68a73078cf40e9f68f9c3cd9959836855b85b8347c3f7efc6b13201"}, |
| + {"internal/release/retention.go", "claimRawSetRetention", "nonrecord", "sel:Put", "05033e1266623712efe57b39ce282ecddf76d2b774e343df4dc5f81375d11662"}, |
| + {"internal/release/serial.go", "BumpSerial", "nonrecord", "sel:Put", "8b304349553e0daf5ac2affcf4bc5e2abc9ce1a6b3423f624e41f6bd80c8ccf0"}, |
| + {"internal/release/sidecarwake.go", "claimRawTouchSidecarWake", "nonrecord", "sel:Put", "2cf98594adc5096b6a1c69e561e32aabb16341134e226d73467110a6ae3fc686"}, |
| + {"internal/release/store.go", "type Store", "reference", "id:Put", "2ccf0a0f5b63b8670c80c8d3589890d170777c83e60d4f33ea49a177a66084f5"}, |
| + {"internal/release/store.go", "prefixStore.Put", "record-transport", "def:Put,sel:Put", "d257f767e3ecb0f7bcb4e4357cde1e45e869647c4e41aa72fa5782c50123ee16"}, |
| + {"internal/release/store.go", "prefixStore.PutFrom", "record-transport", "def:PutFrom,id:PutObjectFrom", "965ebec4c3349c7c103aab0687f1e08b11c624a13a9cd379658ca7185b3dc5d5"}, |
| + {"internal/release/store.go", "type PutStreamer", "reference", "id:PutFrom", "1e9ac4fc57072be0ed7e8d16b29f3bc588cb1462157bb352861bf072aa81e133"}, |
| + {"internal/release/store.go", "PutObjectFrom", "record-transport", "def:PutObjectFrom,sel:Put,sel:PutFrom", "30dd21f424804c0252b9303a9a96393042651d6fa53ece25ba6cc1af9feff00c"}, |
| + {"internal/release/store.go", "DirStore.Put", "record-transport", "def:Put", "0e834f8b1a78979ef0611b72bc98b24b528acd56809fd1976f2e0b6a0c2ed567"}, |
| + {"internal/release/store.go", "DirStore.PutFrom", "record-transport", "def:PutFrom", "1ddfce76d4f2110505a39916d7bc9855e5fcb51a584c531b36bbc1b65d598dd1"}, |
| + {"internal/release/store.go", "S3Store.Put", "record-transport", "def:Put", "43f9b7beec0d1ef16645473a574082a8fce1526d809536f7b7a48253d3c7b6b0"}, |
| + {"internal/release/store.go", "S3Store.PutFrom", "record-transport", "def:PutFrom", "46f0fc7ca6ce297a121386c78ba9ae2662687253c08a6cda490a63b9054abdce"}, |
| + {"internal/release/system.go", "PutAntivirus", "nonrecord", "sel:Put", "5fac12bfabd74baf7a5a9fc14583a346ccdaa7c6b4acc843365b99bf22e0d47d"}, |
| + {"internal/release/systemidentity.go", "PublishSystemIdentity", "nonrecord", "sel:Put", "2fd9e96c42ac7cd8c568cb77cc99408769954ea4dcaac94bf31b9bc45c18de06"}, |
| + {"internal/release/systemidentity.go", "CreateExact", "nonrecord", "sel:Put", "ebdaf47c1787fc6a6330e835aec0b0eb7c23ef8f07dc078ae6ab60013d4d5f58"}, |
| + {"internal/release/systempublication.go", "PublishSystemPublication", "nonrecord", "sel:Put", "4c0490ce92f910ca17654f80b99d9d6dbd8c68b3bb76851223c77aca73bc38ed"}, |
| + {"internal/release/systempublication.go", "CancelSystemPublication", "nonrecord", "sel:Put", "bd61ab14579121a194bdf60b23d000b3ade348e429542b0410fe5fdf3415fe43"}, |
| + {"internal/release/target.go", "setTargetUnchecked", "nonrecord", "sel:Put", "1217704647ff19e95bdcb6f22c62a08579cea00cd0892cc2ec51438f07390b45"}, |
| + {"internal/release/trash.go", "claimRawTrashApp", "nonrecord", "sel:Put", "c7a5f36b7866a98eebc3dc6cf153e809c7cdae635bb83b010811cc5246658789"}, |
| + {"internal/release/workerassignment.go", "type WorkerControlStore", "reference", "id:Put", "2d709d8015f70a1c476cd02dade0d71103959faf8bffd38b20869692f461dcca"}, |
| + {"internal/release/workerassignment.go", "CreateWorkerAssignment", "nonrecord", "sel:Put", "24d684bed7a466eb4a79b48c170567a6935d3e3880cf9c7a5b4d8493e00bbd23"}, |
| + {"internal/release/workerassignment.go", "AdvanceWorkerAssignment", "nonrecord", "sel:Put", "668ed1ec778a8ad91a413f8a7884ed42f97482362a9447fb56597c41878eae06"}, |
| + {"internal/schedule/declaration.go", "modify", "nonrecord", "sel:Put", "0e0aca1879e3eae4c4deed779f6c50f6cb1b9c3efb6a28cd6c226f4e8fc7c570"}, |
| + {"internal/schedule/declaration.go", "claimRawTouchRun", "nonrecord", "sel:Put", "4d25b353bc8ae5c16b11b848446c46b59128da0176f3c9584052ba5158abc829"}, |
| + {"internal/schedule/status.go", "claimRawWriteStatus", "nonrecord", "sel:Put", "ebad18e2a51ccd81fe73023cb18e475073eebba0a0c754cd858aae6a5dc72d58"}, |
| + {"internal/status/fleet.go", "putOverwrite", "nonrecord", "sel:Put", "527894a121c90ff71bc0ed9758ca423a576990456d5751f82fe7963dd77dc3c6"}, |
| + {"internal/status/snapshot.go", "WriteBox", "nonrecord", "sel:Put", "7583ce524d0606dbf9e5d60cdd550be8d0f6080e72737ad7f5c9043843d83394"}, |
| + {"internal/steering/route53.go", "gcHealthChecks", "key-site", "key:recordKey", "8f73ccc805fad4b9442a283c193eef72f9dacc19dcf75afab4d4f5707a41b7fa"}, |
| + {"internal/steering/route53.go", "recordKey", "key-site", "key:recordKey", "44de1074a7fdcd31ef0da6f187c4d0554949e774b315a7bfacc0b65b44b4226f"}, |
| + {"internal/steering/route53.go", "partitionManaged", "key-site", "key:recordKey", "f4d5ee2ee3afc988a7725a4f6ed6d9edfc01d08f5c44f57492d2f282fdee281a"}, |
| + {"internal/steering/route53.go", "recordChanges", "key-site", "key:recordKey", "d057e8edd577f1e8fd9b48936f96f108cbd543d9133b17073d8c29bb67500c82"}, |
| + {"internal/telemetry/telemetry.go", "Update", "nonrecord", "sel:Put", "7b0af146e7cd4904f2e5294de91eb287154f146e1c3d6b7a26e1a4e58a71a975"}, |
| + {"internal/usage/edge.go", "UpsertEdgeDay", "nonrecord", "sel:Put", "ddd80746e03de810dbc2147195ef6a319292da124c02358e06eb17f10194946b"}, |
| + {"internal/usage/statement.go", "UpsertStatement", "nonrecord", "sel:Put", "d4e69ee3b62fb292111680f1119fab74749ae4aa6d9ab09c75a839b47ee61fda"}, |
| + {"internal/usage/statement.go", "CreateDayObject", "nonrecord", "sel:Put", "047ea4bfa15d794d655f13dab54720fea80ee9a2e8a25be02a4c4c1d0b71fc81"}, |
| +} |
| + |
| +// END REVIEWED INVENTORY |
| +``` |
| + |
| +#### New file: `internal/console/plan9_b8_sequence_stack_test.go` |
| + |
| +```go |
| +package console |
| + |
| +import ( |
| + "context" |
| + "crypto/ed25519" |
| + "encoding/json" |
| + "testing" |
| + "time" |
| + |
| + "github.com/carlosframework/platform/internal/hostops" |
| + "github.com/carlosframework/platform/internal/provision" |
| + "github.com/carlosframework/platform/internal/release" |
| +) |
| + |
| +// Exercise the actual wrapper composition. The injected checker permits |
| +// these test writes; this tests encoding/context propagation, not authority. |
| +func TestPlan9B8SequenceThroughConsoleEffectAndMutationStack(t *testing.T) { |
| + ctx := context.Background() |
| + raw := release.NewDirStore(t.TempDir()) |
| + pub, _, err := ed25519.GenerateKey(nil) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + now := time.Now() |
| + w, err := hostops.NewControlWriter(raw, pub, func() time.Time { return now }) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + a := hostops.AppAdmission{V: 1, AppID: "app-id", Revision: 1, Epoch: 1, State: "open", Registrations: map[string]hostops.Registration{}, Effects: map[string]hostops.Effect{}} |
| + body, _ := json.Marshal(a) |
| + if _, err = raw.Put(ctx, "apps/bab/site/admission.json", body, ""); err != nil { |
| + t.Fatal(err) |
| + } |
| + owner := hostops.RegistrationOwner{Console: "console", Enrollment: "enrolled", Controller: "controller"} |
| + h, err := w.RegisterStart(ctx, "bab", "site", "app-id", "registration", hostops.Registration{Kind: "create", Host: "one.example.com", Claim: hostops.SavedClaim{Absent: true}, Owner: owner, AppID: "app-id", Epoch: 1, CreatedAt: now}, now) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + records, err := hostops.NewConditionalRecordStore(raw, pub) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + observed := 0 |
| + data, err := hostops.NewObservedMutationBoundary(true, records, func(context.Context, hostops.MutationTarget) error { return nil }, func(e hostops.MutationEvent) { |
| + if e.Verdict == nil { |
| + observed++ |
| + } |
| + }) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + store := claimEffectStore{data: data, effects: claimStoreEffects{OperationID: "registration", Runner: h.Runner(func() time.Time { return now })}} |
| + rec := provision.Record{Schema: 1, Account: "bab", App: "site", Host: "one.example.com", Region: "ie", Channel: "stable", CreatedAt: now} |
| + // Seed legacy bytes outside the claim stack, then let the inner boundary |
| + // introduce the sequence. The typed outer writer must return exactly one. |
| + if err = provision.CreateRecord(ctx, raw, rec); err != nil { |
| + t.Fatal(err) |
| + } |
| + got, err := provision.SetRecordMove(ctx, store, rec.Account, rec.App, rec.Host, func(*provision.Record) error { return nil }) |
| + if err != nil || got.RecordSeq != 1 { |
| + t.Fatalf("first claim write: %+v %v", got, err) |
| + } |
| + got, tag, err := provision.GetRecordETag(ctx, store, rec.Account, rec.App, rec.Host) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + got.Channel = "edge" |
| + if err = provision.UpdateRecord(ctx, store, *got, tag); err != nil { |
| + t.Fatal(err) |
| + } |
| + afterUpdate, updateReadErr := provision.GetRecord(ctx, raw, rec.Account, rec.App, rec.Host) |
| + if updateReadErr != nil || afterUpdate.RecordSeq != 2 { |
| + t.Fatalf("whole-record update: %+v %v", afterUpdate, updateReadErr) |
| + } |
| + got, err = provision.SetRecordMove(ctx, store, rec.Account, rec.App, rec.Host, func(*provision.Record) error { return nil }) |
| + if err != nil || got.RecordSeq != 3 { |
| + t.Fatalf("populated writes: %+v %v", got, err) |
| + } |
| + current, err := provision.GetRecord(ctx, raw, rec.Account, rec.App, rec.Host) |
| + if err != nil || current.RecordSeq != got.RecordSeq { |
| + t.Fatalf("returned vs stored: %+v %v", current, err) |
| + } |
| + side := provision.SidecarRecord{Schema: 1, Account: "bab", App: "site", Name: "worker", Host: provision.SidecarHost("worker", "site", "bab"), CreatedAt: now} |
| + if err = provision.CreateSidecarRecord(ctx, store, side); err != nil { |
| + t.Fatal(err) |
| + } |
| + body, _, err = raw.Get(ctx, provision.SidecarRecordKey("bab", "site", "worker")) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + _, seq, err := provision.RecordSequence(body) |
| + if err != nil || seq != 1 || observed != 4 { |
| + t.Fatalf("sidecar seq=%d observations=%d err=%v", seq, observed, err) |
| + } |
| +} |
| +``` |
| + |
| ## Group C: rollout ordering and witnesses |
| ### Task C1: certify every strict-decoded field (R1, 5-M4, first-review M5) |
| @@ -6616,7 +8201,7 @@ The mandatory release inventory is: |
| | `region`, `claim_etag`, `observed_boot_id` | `hostops.StopReceipt` plus claim regional bindings | Root, console and regional readers verify identical receipt bytes and reject a changed value. | |
| | `park_target`, `evidence_refs` | `hostclaim.Op` and retirement evidence | Populated claims round-trip, including every keyed evidence reference. | |
| | `acq` | `hostclaim.Holder`, records and ownership backfill | Strict read of the first acquired holder before #232 can write it. | |
| -| `record_seq` | B8: `provision.Record`, `provision.SidecarRecord`, every claim-on conditional record writer and the move-start resolver | Read legacy absent/zero and populated counters on every participating binary; ordinary/lifecycle/effect/stream writes advance without losing precision, and an ordinary rewrite cannot reopen the failed Put's fence. | |
| +| `record_seq` | B8: `provision.Record`, `provision.SidecarRecord`, every provision record writer, console boundary and move-start resolver | Read legacy absent/zero and populated counters on every participating binary; ordinary/lifecycle/effect/stream and raw box writes advance populated counters without losing precision, and an ordinary rewrite cannot reopen the failed Put's fence. | |
| Also inventory `Box.CensusPublicKey`, the release identity fields and every new field approved for Task B4. The first-review M5 remains open as a deployment ordering requirement: `acq` is on main, but its presence in the type does not prove that every deployed reader understands it. Do not remove the field to make old readers pass. All named fields must be in the release certification, even when their test fixture normally omits them. |
| @@ -12170,7 +13755,7 @@ Coverage: |
| | R1 / 5-M4 | C1, every named strict field and exact binary/receipt certification. | |
| | M-4 / M-7 | C2/C3, missing evidence producers remain refusal; journal-owned staging retained. | |
| | First-review M7 | C3, one-box-region guard contract and production witness. | |
| -| Move-start recovery | B8 rev 3, complete resolution through the exact runner; monotonic `record_seq` fences every failed Put, with passing acceptance, drain/retry and ordinary-rewrite ABA regressions. | |
| +| Move-start recovery | B8 rev 4, complete resolution through the exact runner; monotonic `record_seq` fences every failed Put, with passing acceptance, drain/retry and ordinary-rewrite ABA regressions. | |
| | Moves C1 H3 / H2 / H12 / H15 / H16 | D1-D5, guarded phase progression, attempts, readiness, retirement and owner dispatch; production dependencies G-1 through G-6 remain explicit. | |
| | I-3 / I-5 / M1-1 | Already fixed on main; no duplicate implementation. Keep wrapped-not-found, bounded binary reads and locked/torn marker recovery regression coverage. | |
| @@ -12207,3 +13792,33 @@ Opus performed a writer audit and bounded authority reviews. The audit found the |
| One broader console probe remains red independently of B8: `TestMoveDoorHostClaimRefusals/target-fenced` returns 503 where the fixture expects 409. A separate clean `4f26f15` export with only rev 2's A2 applied reproduced the same failure, before any B8 code. That fixture was not changed by this scoped revision, and the passing focused console command does not claim to cover it. B4 and B7's decision tests remain intentionally red; the six Paul decisions and Group D's production dependencies G-1 through G-6 remain open. Rev 3 completes B8's planned implementation, not activation readiness as a whole. |
| Only this plan is saved in the requested worktree. There are no git writes or live operations. All authoring overlays, review inputs, logs and scratch are removed at hand-back; the shared Go cache remains shared. |
| + |
| +### Rev 4 verification (2026-10-08) |
| + |
| +Remote `main` was checked without fetching or writing git state and remained `4f26f15cb41ecfdd9214b49393c228399f34490b`. The rev 4 addendum is implemented in B8's complete blocks: provision owns sequencing, populated records advance without a claim context, first claim writes introduce the counter, and legacy records with no counter retain their exact main serialization. Both instance and sidecar keys are covered. The census pins 353 reviewed production declarations across the repo, including command writers, variable-key transports, method values, key constructors and record-path literals. Its policy permits one record Put primitive and no raw record writer. Added or changed sites require source and caller review; the census is a review gate, not whole-program data-flow analysis. |
| + |
| +Negative evidence: an isolated export with only the old A2 blocks reproduced `TestMoveDoorHostClaimRefusals/target-fenced` returning 503 rather than 409. Its fixture named the terminated box as its own witness. A2 now supplies a separate active, unfenced peer and retains a self-witness refusal case. The first complete console run also found the missing activation-cancel route census entry; A2 now includes that patch. Against rev 3, the new raw-box regression seeded sequence one, then wrote without the context and incorrectly returned one again. Rev 4 returns two. The rev 3 create path also failed the first-claim create regression. B8 retains all earlier acceptance tests, including the ordinary-rewrite ABA case, and adds raw-box fencing proofs, CAS retry, whole-record raw-base validation, sidecar/create bytes, migration preflight and the complete console store-wrapper composition. |
| + |
| +Every executable block was replayed into a fresh export with `patch --fuzz=0`; all 114 resulting files matched the verified overlay. The six complete decision task sections (A1, A3, B3, B4, B7 and C2) and the entire Group D section were compared with rev 3 and remain byte-identical. All Group D signatures are therefore unchanged. The default Go cache was read-only, so checks used the brief's shared fallback cache. All Go checks ran serially under `nice -n 10`, tests used `-p 1`, and socket tests used the newly created `/tmp/g9`. |
| + |
| +The complete `internal/provision`, `internal/hostops` and `internal/console` packages passed without test-name filters; the final full console run took 284.248 seconds. As required by this plan's application instructions, B4 and B7's decision-gated test-only blocks were withheld from the decided-fix runtime overlay, then replayed and compiled separately. Their implementation decisions remain open; no A2 or B8 case was skipped or excluded by these commands. Final census/assertion tightening was followed by another complete provision/hostops run and passing focused console regressions. The final executed commands were: |
| + |
| +```bash |
| +export GOCACHE=/home/paulca/.cache/tmp/claude-1001/gocache |
| +export GOFLAGS=-buildvcs=false |
| +export TMPDIR=/tmp/g9 |
| +nice -n 10 go test -p 1 ./internal/provision ./internal/hostops ./internal/console -count=1 |
| +nice -n 10 go test -p 1 ./internal/provision ./internal/hostops -count=1 |
| +nice -n 10 go test -p 1 ./internal/console -run 'Plan9B8|MoveDoorHostClaimRefusals|MoveDoorRefusesAnIneligibleSource|ClaimConsoleRouteCensus' -count=1 |
| +nice -n 10 go test -p 1 ./internal/hostfloor ./internal/hostactors ./internal/hostfence ./internal/registry ./internal/hostclaim ./internal/hostbox ./internal/host ./internal/activator ./internal/hostguard ./internal/park . -run 'Plan9|MovesPass|Phase1|Cut|Landing|AccountsMigrate|Migration' -count=1 |
| +nice -n 10 go vet ./internal/provision ./internal/hostops ./internal/console ./internal/host |
| +nice -n 10 go build -p 1 ./... |
| +nice -n 10 go test -p 1 ./... -run '^$' |
| +# After replaying the unchanged B4/B7 test-only blocks: |
| +nice -n 10 go test -p 1 ./internal/console ./internal/hostbox -run '^$' |
| +gofmt -l . |
| +``` |
| + |
| +The whole-tree compile check covered all 93 package entries; formatting and vet were clean. The focused command selected no park tests, so it is not evidence of a park runtime suite. No full-repository runtime suite, cross-build, cloud/IAM test or privileged rollout witness was rerun in rev 4. Sonnet audited writers and diagnosed the A2 fixture; Opus reviewed the authority changes and their corrections, with no confirmed P1/P2 remaining. Follow-up checks tightened the census against literal-built keys in transports and checked each console-stack increment separately. The raw whole-record path now validates the actual stored bytes; all sequence predicates use provision's shared key classifier. |
| + |
| +Rev 4 supersedes the A2 failure caveat in rev 3. It completes the decided A2 and B8 plan changes, not the six unresolved decisions or Group D's production dependencies. No floor, flag, deployment or production resource changed. Only this plan is saved in the requested worktree, with no git writes. All authoring overlays, delegate inputs, logs and scratch, including `/tmp/g9`, are removed at hand-back; the shared Go cache is retained. |