feat(k8s): the ConfigMap effect-receipt row, observed, guarded and never pruned (#2074) - #2210
Merged
Conversation
…ver pruned (#2074) The only receipt materialization row was aws SSM (#1835), so a project whose effects run against a cluster and never touch AWS had no receipt target: `effect()` could not record that it fired and WatchOp's staleness phase had nothing to read. This is the k8s row, built to the same shape. - `EffectReceipt` under `lexicon: "k8s"`, entityType `K8s::Core::ConfigMap`, named `chant-receipt.<stack>.<env>.<effect>` from the ownership fields that stamp markers, env explicit (epic decision 4). The `.` separator is excluded from every segment, so two identities cannot render one name. The namespace is `k8s.receipts.namespace`, `default` when unset, never invented. - The serializer renders the rows into a trailing `# chant:effect-receipts` comment rather than a document. Kubernetes YAML has no metadata channel outside the documents, and a document is exactly what an applier applies, so the comment is the k8s equivalent of the aws row's template `Metadata`: visible to the observation leg, structurally unreachable from `applyManifest`. - `k8sReceiptStore` over the typed client implements core's receipt seam, and the op activities barrel exports `receiptRead`/`receiptWrite`/ `receiptStaleness` bound to it, which is WatchOp's staleness leg. - The observation reads the receipt back on both the thin and the deep paths. The thin read maps the stored value onto `attributes.value`, where `readReceiptValue` looks; the deep read reports presence with an EMPTY property tree, so a receipt is never property drift and never an unclaimed field (#2160) on any reading. - `delete: "owned-only"` retains the receipt instead of pruning it. A receipt is chant-owned and is in no apply set by construction, which is the exact shape the sweep deletes, so it takes the generated-once Secret treatment. - COR022 and COR023 fixtures over the ConfigMap row, and the concept page and the k8s serialization page name it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FoXyD9UvKQ5ZdhiR9JT1yB
The #2074 rewrite of the materialization-row paragraph into a table dropped one em dash from the page, so its gated count is 29 rather than 30. Ratchet the entry rather than leave the baseline one above what the page costs. Only this page's entry moves. The suite is a further one below the baseline after #2198, which belongs to that change to ratchet. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FoXyD9UvKQ5ZdhiR9JT1yB
…ts on its own branch The brief forbids em dashes in prose written for this change; twenty-eight came in across code comments, doc comments, test names and one line of the effect-receipts concept page. Each becomes a comma, a period or a colon as the sentence needs. No behaviour changes, and no pre-existing line is touched. The receipt branch of pruneOrphans also moves below the generated-once branch. It was inserted between the generated-once comment and the `if` that comment describes, which read as though the comment belonged to the receipt check. Removing the concept page's em dash drops its gated count again, so its baseline entry follows it down from 29 to 28. Only that entry moves. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FoXyD9UvKQ5ZdhiR9JT1yB
lex00
added a commit
that referenced
this pull request
Sep 7, 2026
…2214) Peer session dropped findings in #2198, #2203, #2206; this session dropped findings in #2208, #2210, #2213. Each PR deferred the ratchet so the drops would not conflict on the same baseline file, per PR #2174's precedent that the baseline is ratcheted down rather than left slack. Claude-Session: https://claude.ai/code/session_01FoXyD9UvKQ5ZdhiR9JT1yB Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Item 1 of #2074. Item 2 stays open on the issue with a comment recording what a decision needs.
The problem
The only receipt materialization row was aws SsmParameter, so a cluster-only project could not use
effect()receipts or WatchOp stale-receipt reporting at all, even though the effect-receipts concept page documents the feature without qualification.What this lands
A k8s ConfigMap receipt row:
EffectReceiptunder the k8s lexicon with entityTypeK8s::Core::ConfigMap, carrying the core marker so lint, plan and apply recognize it lexicon-independently. The resolution split is unchanged from the aws row: static hashes at synthesis, references render the placeholder note.The receipt ConfigMap is named
chant-receipt.<stack>.<env>.<effect>, from the same ownership fields that stamp markers, with.as the separator because no segment may contain one, so two different stacks can never render the same name. It lives ink8s.receipts.namespacefromchant.config.ts, defaulting todefault, never derived from the stack or environment, because a guessed namespace is one chant would then have to create.Three legs are covered against the fake cluster. The store server-side-applies the ConfigMap.
describeResourcesreads it back where core'sreadReceiptValuelooks, andplanReceiptsturns that into an effect row when stale and a noop when applied. The WatchOp staleness phase reports absent, differs or clean and issues no write.Two properties the orchestrator asked for. The written ConfigMap carries the ownership marker labels plus an effect-receipt label, and
pruneOrphansnow reports it retained rather than deleting it, the same treatment a generated-once Secret gets, because a receipt is chant-owned and in no apply set by construction. The deep observation leg returns presence and uid with an empty property tree, so a receipt is never drift and never an unclaimed field; a test drives the real deep diff with the real k8s hooks over a stale receipt and asserts every bucket is empty.The rendering channel
The aws row parks receipt rows in the CloudFormation template's Metadata, outside Resources. Kubernetes YAML has no channel outside the documents, and a document is precisely what an applier applies, so the k8s rows ride a trailing
# chant:effect-receipts <json>comment on the manifest output.loadAllyields no object for it, so a receipt is structurally unreachable fromapplyManifest, while it still rides the one build string the observation legs receive. A test asserts a manifest plus the comment parses to exactly one document. The alternative, a sidecar file, was rejected because it would cost the observation leg its address source.Docs
The concept page's aws sentence became a two-row table plus a
kubectl get configmapwalk-away example and a paragraph on the prune exclusion. The k8s serialization page gained the row; its twin was regenerated. COR022 and COR023 fixtures cover the k8s row.The docs prose baseline entry for the effect-receipts page is ratcheted for the findings this branch removed. The gate reports three further below baseline that belong to the peer's #2198, #2203 and #2206.
Gates
Run by the orchestrator on the rebased head after the agent's own run.
Read first
lexicons/k8s/src/serializer.tsfor the trailing-comment channel, thenreceipt-store.tsfor the address derivation, then the deep-observe test asserting the receipt is never drift.🤖 Generated with Claude Code
https://claude.ai/code/session_01FoXyD9UvKQ5ZdhiR9JT1yB