arcane-cli 0.29.0 → 0.29.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,7 +12,7 @@ Pipeline patterns, branch policies, and deployment gates for all your projects.
12
12
 
13
13
  ## Executive Summary
14
14
 
15
- - Every code repository must have a CI pipeline that runs on PR creation and merge to main.
15
+ - Every code repository must have a CI pipeline that runs on PR creation and merge to main. **Enforcement: explicitly advisory prose (ARC-023) — prescriptive guidance for repos Prospero provisions; Arcane's own tooling does not enumerate an organization's repositories to verify each one has a CI pipeline.**
16
16
  - Branch policies enforce PR requirements: build must pass, reviewer required, work item linked.
17
17
  - Terraform changes use plan-on-PR / apply-on-merge with manual approval gates.
18
18
  - Prospero owns pipeline creation and maintenance across all repos.
@@ -39,12 +39,12 @@ Applied to all code repositories (not docs-only repos per ADR-048):
39
39
 
40
40
  | Policy | Setting | Rationale |
41
41
  |--------|---------|-----------|
42
- | **Require PR** | All branches → main | No direct pushes to main |
43
- | **Minimum reviewers** | 1 (Lince for code, Merlin for architecture) | Quality gate |
44
- | **Build validation** | CI pipeline must pass | No merging broken code |
45
- | **Work item linking** | Required (when ticketing active) | Traceability |
46
- | **Comment resolution** | All comments must be resolved | No ignored feedback |
47
- | **Merge type** | Merge (no fast-forward) or Rebase and fast-forward — Squash disallowed | Preserves per-commit attribution trailers (ARC-009 §7); see [[git-conventions#Azure DevOps PR Merge Type]] |
42
+ | **Require PR** | All branches → main | No direct pushes to main. Enforcement: explicitly advisory prose (ARC-023) — prescriptive for consumer Azure DevOps repos; no Arcane check verifies a PR-required policy is configured. |
43
+ | **Minimum reviewers** | 1 (Lince for code, Merlin for architecture) | Quality gate. Enforcement: explicitly advisory prose (ARC-023) — prescriptive for consumer Azure DevOps repos; no Arcane check verifies configured reviewer counts. |
44
+ | **Build validation** | CI pipeline must pass | No merging broken code. Enforcement: explicitly advisory prose (ARC-023) — prescriptive for consumer Azure DevOps repos; no Arcane check verifies a build-validation policy is configured. |
45
+ | **Work item linking** | Required (when ticketing active) | Traceability. Enforcement: explicitly advisory prose (ARC-023) — prescriptive for consumer Azure DevOps repos; no Arcane check verifies it. |
46
+ | **Comment resolution** | All comments must be resolved | No ignored feedback. Enforcement: explicitly advisory prose (ARC-023) — prescriptive for consumer Azure DevOps repos; no Arcane check verifies a comment-resolution policy is configured. |
47
+ | **Merge type** | Merge (no fast-forward) or Rebase and fast-forward — Squash disallowed | Preserves per-commit attribution trailers (ARC-009 §7); see [[git-conventions#Azure DevOps PR Merge Type]]. Enforcement: verified external platform policy (ARC-023) — `spell doctor`'s `checkPlatformBranchPolicy` (src/commands/doctor.ts, backed by src/modules/platform-policy.ts's evaluateGitHubMergePolicy/evaluateAdoMergePolicy) queries live GitHub Rulesets or Azure DevOps branch policy and verifies the effective merge methods match this ladder — a local, on-demand CLI check the operator runs, not a check wired into CI itself. |
48
48
 
49
49
  For docs-only repos (Arcane):
50
50
  - PR not required (ADR-048 allows local ff-only merge for docs)
@@ -64,7 +64,7 @@ glob, and deliberately just one entry: `docs/**`/`journal/**`/`.arcane/governanc
64
64
  directory-wide excludes would ALSO exclude any non-Markdown file a consumer happens to place in
65
65
  those directories (a build script, a manifest, tooling config) — silently violating ARC-022's own
66
66
  unconditional requirement that scripts, manifests, lockfiles, migrations, containers, and
67
- infrastructure always remain triggering inputs, regardless of which directory they live in. Filetype-scoping to
67
+ infrastructure always remain triggering inputs, regardless of which directory they live in. **Enforcement: explicitly advisory prose (ARC-023) — prescriptive for the Azure DevOps template this repo ships to consumers; Arcane has no shipped `azure-pipelines.yml` file of its own to check parity against, and cannot verify a downstream repo kept the filter filetype-scoped.** Filetype-scoping to
68
68
  `**/*.md` closes that gap: only actual Markdown files are excluded, anywhere in the tree; anything
69
69
  else — including a `.md`-adjacent script — still triggers CI by default.
70
70
 
@@ -111,7 +111,7 @@ steps:
111
111
  Previously had no path filter at all — fail-safe (never misses a code change) but wasteful (a
112
112
  pure docs commit still burns a full pipeline run). Fail-safe path filter (ARC-022): the same
113
113
  filetype-scoped `**/*.md` **exclude** entry as the .NET template above — never a directory-wide
114
- exclude, and never an include list.
114
+ exclude, and never an include list. **Enforcement: explicitly advisory prose (ARC-023) — prescriptive for the Azure DevOps template this repo ships to consumers; Arcane has no shipped `azure-pipelines.yml` file of its own to check parity against, and cannot verify a downstream repo kept this filter filetype-scoped.**
115
115
 
116
116
  ```yaml
117
117
  trigger:
@@ -149,7 +149,7 @@ steps:
149
149
  ### Terraform Pipeline
150
150
 
151
151
  An **include** filter is correct here, not the ARC-022 anti-pattern — but only if it's a
152
- **filetype** glob, not a directory-prefix one. An earlier version of this template included only
152
+ **filetype** glob, not a directory-prefix one. **Enforcement: explicitly advisory prose (ARC-023) — prescriptive for the Azure DevOps template this repo ships to consumers; Arcane has no shipped `azure-pipelines.terraform.yml` file of its own to check parity against, and cannot verify a downstream repo kept the include filter filetype-scoped rather than directory-prefixed.** An earlier version of this template included only
153
153
  `infrastructure/terraform/**`: a directory-prefix pattern that silently misses any `.tf` file
154
154
  added *outside* that one named directory (a new `modules/**` root, a renamed infra folder) —
155
155
  exactly the "new code locations fail open" failure ARC-022 rejects, and exactly what EF-22's own
@@ -244,7 +244,7 @@ steps:
244
244
  CI skip/run decisions are based **only on changed paths** — never on commit message, author
245
245
  identity, or branch name. Do not add a *new* mechanism (a custom `docs(...)`-style commit-prefix
246
246
  convention, a bot rule, a pipeline-level check) that lets a commit opt itself out of validation by
247
- what it *says* rather than what it *changed*.
247
+ what it *says* rather than what it *changed*. **Enforcement: explicitly advisory prose (ARC-023) — prescriptive guidance for Azure DevOps consumer pipelines; this repo's own `.github/workflows/ci.yml` runs unconditionally on every push/PR to `main` with no path filter at all, so it neither implements nor is bound by this pattern, and no Arcane check inspects a downstream repo's CI configuration for compliance.**
248
248
 
249
249
  Commit messages, authorship, and branch names are attacker-controlled metadata — anyone who can
250
250
  push a commit controls all three. A CI-skip mechanism keyed on any of them is a bypass primitive:
@@ -253,7 +253,7 @@ actually touched. Path-based filtering (above) is the only trust signal this rep
253
253
  recognizes, because the pipeline itself independently observes the changed paths rather than
254
254
  trusting a claim embedded in the commit.
255
255
 
256
- **Know the platform default, don't assume this rule alone controls it.** Azure Pipelines honors
256
+ **Know the platform default, don't assume this rule alone controls it. Enforcement: explicitly advisory prose (ARC-023) — this paragraph discloses a residual platform-level gap; it is not a mechanism Arcane enforces.** Azure Pipelines honors
257
257
  `[skip ci]`, `[ci skip]`, `skip-checks: true`, `[skip azurepipelines]`, `[skip azpipelines]`,
258
258
  `[skip azp]`, and `***NO_CI***` in a pushed commit message **by default**, on `trigger:`-driven
259
259
  runs — this is platform behavior, not something a consumer opts into, and this governance rule
@@ -283,7 +283,7 @@ adopted exclude-based YAML triggers), a change to a new top-level code directory
283
283
  triggers the pipeline (YAML is fail-safe) but does **not** require it to pass before merge (branch
284
284
  policy is not) — the build can run, fail, and be ignored, or the PR can merge before it finishes.
285
285
  Whenever a pipeline's YAML trigger scope changes, the branch policy's own path filter (if it has
286
- one) must be reviewed and updated to match or be a strict superset — never left narrower.
286
+ one) must be reviewed and updated to match or be a strict superset — never left narrower. **Enforcement: explicitly advisory prose (ARC-023) — prescriptive for consumer Azure DevOps projects; Arcane has no visibility into a downstream repo's branch-policy path-filter configuration and cannot verify the two stay aligned.**
287
287
 
288
288
  ---
289
289
 
@@ -291,9 +291,9 @@ one) must be reviewed and updated to match or be a strict superset — never lef
291
291
 
292
292
  | Environment | Gate | Who Approves |
293
293
  |-------------|------|-------------|
294
- | **Staging** | CI passes + PR approved | Automatic on merge |
295
- | **Production** | Staging verified + manual approval | Human (operator) |
296
- | **Terraform apply** | Plan reviewed + Checkov clean | Human (operator) |
294
+ | **Staging** | CI passes + PR approved | Automatic on merge. Enforcement: explicitly advisory prose (ARC-023) — describes a consumer Azure DevOps pipeline's intended gating; Arcane does not verify a downstream repo's environment configuration. |
295
+ | **Production** | Staging verified + manual approval | Human (operator). Enforcement: explicitly advisory prose (ARC-023) — describes a consumer Azure DevOps environment's approval configuration; Arcane does not verify it is actually configured. |
296
+ | **Terraform apply** | Plan reviewed + Checkov clean | Human (operator). Enforcement: explicitly advisory prose (ARC-023) — describes a consumer Azure DevOps pipeline's manual-apply step; Arcane does not verify it is actually configured. |
297
297
 
298
298
  ---
299
299
 
@@ -67,8 +67,7 @@ with EU visitors signing up for a free product is a plausible trigger; a company
67
67
  not, but "zero" needs to be an actual verified fact about the user base, not an assumption based on
68
68
  where the company is headquartered.
69
69
 
70
- **Rule (CS-01):** Determine GDPR applicability from the actual or intended user base's location and
71
- what the processing does, never from the business's own location alone.
70
+ **Rule (CS-01): Determine GDPR applicability from the actual or intended user base's location and what the processing does, never from the business's own location alone. Enforcement: explicitly advisory prose (ARC-023) — GDPR applicability turns on real-world facts about a specific business's actual user base and processing, a legal/business judgment call no Arcane mechanism observes or verifies.**
72
71
 
73
72
  ### CS-02: A lawful basis and enforceable data-subject rights
74
73
 
@@ -80,8 +79,7 @@ Requests exercising these rights carry a response deadline (commonly one month,
80
79
  cases) — a rights-request process that has no defined owner or turnaround time is itself a gap, even
81
80
  before any request arrives.
82
81
 
83
- **Rule (CS-02):** Document a lawful basis per processing category, and maintain a working process — with
84
- a defined owner and turnaround time — for handling data-subject rights requests.
82
+ **Rule (CS-02): Document a lawful basis per processing category, and maintain a working process — with a defined owner and turnaround time — for handling data-subject rights requests. Enforcement: explicitly advisory prose (ARC-023) — whether a lawful basis is properly documented and a rights-request process actually functions is a substantive judgment about the downstream product's own operations; `spell-compliance` can only ask and report on it, never verify or gate on it.**
85
83
 
86
84
  ### CS-03: 72-hour breach notification
87
85
 
@@ -90,8 +88,7 @@ relevant supervisory authority within 72 hours of the organization becoming awar
90
88
  starts at awareness, not at confirmation of scope — an incident-response process that only begins
91
89
  drafting a notification after a full investigation completes risks missing the window entirely.
92
90
 
93
- **Rule (CS-03):** Have a breach-notification process that can produce an initial report to the
94
- supervisory authority within 72 hours of awareness, even before the full scope of an incident is known.
91
+ **Rule (CS-03): Have a breach-notification process that can produce an initial report to the supervisory authority within 72 hours of awareness, even before the full scope of an incident is known. Enforcement: explicitly advisory prose (ARC-023) — whether an incident-response process would actually meet the 72-hour window in a real breach is a judgment call about the team's own readiness that no Arcane mechanism tests.**
95
92
 
96
93
  ---
97
94
 
@@ -107,9 +104,7 @@ number here as current). Covered businesses must honor consumer rights to know w
107
104
  it, correct inaccuracies, and opt out of the sale or sharing of their personal information, plus limit
108
105
  use of sensitive personal information on request.
109
106
 
110
- **Rule (CS-04):** Verify CCPA's current statutory thresholds against the business's actual revenue and
111
- data volume, rather than assuming applicability (or non-applicability) from the business's category
112
- alone.
107
+ **Rule (CS-04): Verify CCPA's current statutory thresholds against the business's actual revenue and data volume, rather than assuming applicability (or non-applicability) from the business's category alone. Enforcement: explicitly advisory prose (ARC-023) — confirming current statutory thresholds against a business's actual revenue and data volume is a legal/business determination outside anything Arcane can check itself.**
113
108
 
114
109
  ### CS-05: A real opt-out mechanism, not just a policy statement
115
110
 
@@ -119,9 +114,7 @@ or an equivalent global-privacy-control-honoring mechanism. Stating an opt-out r
119
114
  policy's prose, without a working mechanism a consumer can actually use, does not satisfy the
120
115
  requirement.
121
116
 
122
- **Rule (CS-05):** Verify the opt-out mechanism itself functions end-to-end — a link that exists in the
123
- footer but doesn't actually stop the described sharing is not compliant, regardless of what the privacy
124
- policy says.
117
+ **Rule (CS-05): Verify the opt-out mechanism itself functions end-to-end — a link that exists in the footer but doesn't actually stop the described sharing is not compliant, regardless of what the privacy policy says. Enforcement: explicitly advisory prose (ARC-023) — `spell-compliance`'s Phase 2 only checks whether an opt-out mechanism's indicators are present, not whether it actually stops sharing end-to-end, so confirming real function stays a human verification step.**
125
118
 
126
119
  ---
127
120
 
@@ -135,8 +128,7 @@ feature), and distinguishable from other terms — not buried in a general terms
135
128
  Pre-ticked checkboxes, and consent bundled with acceptance of unrelated terms, do not qualify as valid
136
129
  consent under either framework.
137
130
 
138
- **Rule (CS-06):** Any consent mechanism must be opt-in (not pre-ticked), specific to its actual purpose,
139
- and separable from acceptance of unrelated terms.
131
+ **Rule (CS-06): Any consent mechanism must be opt-in (not pre-ticked), specific to its actual purpose, and separable from acceptance of unrelated terms. Enforcement: explicitly advisory prose (ARC-023) — `spell-compliance` can render a PASS/FAIL/PARTIAL judgment on a consent mechanism in its report, but that report only surfaces a finding for a human to act on and never gates a workflow, so consent validity remains a judgment call.**
140
132
 
141
133
  ### CS-07: A data inventory is the load-bearing prerequisite
142
134
 
@@ -146,8 +138,7 @@ Records of Processing Activities under GDPR Article 30), every downstream rights
146
138
  obligation is unenforceable in practice, even if a policy document claims it's honored — there is no way
147
139
  to actually locate everything that would need to be produced, corrected, or deleted.
148
140
 
149
- **Rule (CS-07):** Maintain a data inventory naming what personal data is collected, where it's stored,
150
- and why, before treating any rights-request or retention process as operational.
141
+ **Rule (CS-07): Maintain a data inventory naming what personal data is collected, where it's stored, and why, before treating any rights-request or retention process as operational. Enforcement: explicitly advisory prose (ARC-023) — `spell-compliance` can surface whether a data-inventory document exists, but it neither verifies the inventory's accuracy nor blocks any workflow on its presence, leaving completeness a human-verified judgment call.**
151
142
 
152
143
  ### CS-08: Retention periods must be defined and actually enforced
153
144
 
@@ -156,8 +147,7 @@ actually deleting it at the end of that period — not simply flagging a record
156
147
  while the underlying data persists indefinitely. An undefined retention period is itself a gap, distinct
157
148
  from and prior to the question of whether deletion is correctly implemented.
158
149
 
159
- **Rule (CS-08):** Define a retention period per data category, and verify deletion is real (data is
160
- actually removed, not merely deactivated) at the end of that period.
150
+ **Rule (CS-08): Define a retention period per data category, and verify deletion is real (data is actually removed, not merely deactivated) at the end of that period. Enforcement: explicitly advisory prose (ARC-023) — whether deletion is actually real rather than merely deactivated requires inspecting the downstream product's live data stores, which is outside anything Arcane's static, read-only self-assessment observes.**
161
151
 
162
152
  ---
163
153
 
@@ -174,9 +164,7 @@ suitably designed at a single point in time; a **Type II** report attests that t
174
164
  operated effectively over an observation period (commonly 3-12 months) — a Type I report does not
175
165
  substitute for a Type II when a customer's requirement specifically calls for one.
176
166
 
177
- **Rule (CS-09):** Confirm which Trust Services Criteria and which report type (I vs. II) a customer's
178
- actual requirement calls for before scoping a SOC 2 engagement — do not assume Security-only Type I
179
- satisfies a requirement that wasn't explicitly checked against what was asked for.
167
+ **Rule (CS-09): Confirm which Trust Services Criteria and which report type (I vs. II) a customer's actual requirement calls for before scoping a SOC 2 engagement — do not assume Security-only Type I satisfies a requirement that wasn't explicitly checked against what was asked for. Enforcement: explicitly advisory prose (ARC-023) — what a specific customer or auditor actually requires is an external fact no Arcane mechanism observes, so confirming it against a live requirement is inherently a human step.**
180
168
 
181
169
  ### CS-10: Access control and audit logging are the operational backbone
182
170
 
@@ -186,8 +174,7 @@ sensitive systems sufficient to reconstruct who did what and when. These are the
186
174
  controls a real security program needs regardless of whether a SOC 2 report is ever pursued — building
187
175
  them only when an audit is scheduled means starting the evidentiary trail late.
188
176
 
189
- **Rule (CS-10):** Implement least-privilege access control, periodic access review, and audit logging of
190
- access to customer-data-holding systems as an ongoing practice, not as audit-time preparation.
177
+ **Rule (CS-10): Implement least-privilege access control, periodic access review, and audit logging of access to customer-data-holding systems as an ongoing practice, not as audit-time preparation. Enforcement: explicitly advisory prose (ARC-023) — this doc states the obligation and points to `threat-model.md`/`hardening-checklist.md` for the control detail without restating or itself verifying it, so no executable check or platform-policy verification is wired to this rule here.**
191
178
 
192
179
  ---
193
180
 
@@ -202,9 +189,7 @@ typical consumer SaaS product with no health-data handling does not trigger HIPA
202
189
  users who happen to be patients elsewhere; the trigger is the product's own handling of PHI, not its
203
190
  users' medical status in general.
204
191
 
205
- **Rule (CS-11):** Confirm HIPAA applicability specifically against whether the product creates,
206
- receives, maintains, or transmits PHI as a covered entity or business associate — do not assume
207
- applicability (or safety from it) based on the target market alone.
192
+ **Rule (CS-11): Confirm HIPAA applicability specifically against whether the product creates, receives, maintains, or transmits PHI as a covered entity or business associate — do not assume applicability (or safety from it) based on the target market alone. Enforcement: explicitly advisory prose (ARC-023) — whether a product actually creates, receives, maintains, or transmits PHI is a factual/legal determination about that specific product that no Arcane mechanism observes.**
208
193
 
209
194
  ---
210
195
 
@@ -226,8 +211,7 @@ applicability (or safety from it) based on the target market alone.
226
211
  starts from real evidence rather than a scramble. HIPAA (CS-11) becomes relevant only if the business
227
212
  is entering healthcare specifically, at whatever stage that happens.
228
213
 
229
- **Rule (CS-12):** Use product stage as a prompt to re-check applicability, not as a substitute for
230
- checking it — a pre-launch product with EU beta users is already in GDPR's scope regardless of stage.
214
+ **Rule (CS-12): Use product stage as a prompt to re-check applicability, not as a substitute for checking it — a pre-launch product with EU beta users is already in GDPR's scope regardless of stage. Enforcement: explicitly advisory prose (ARC-023) — using product stage as a prompt to re-check applicability is itself guidance about when to apply judgment, not a mechanically checkable condition.**
231
215
 
232
216
  ## Related
233
217
 
@@ -44,7 +44,7 @@ This is not a rare edge case specific to any one product; it is a structural pro
44
44
 
45
45
  The only valid evidence of a successful write is: perform the write, then reload or re-fetch through a path independent of the one that performed the write (a fresh page load, a separate read API call, a different client), then compare the observed value against intent field by field. A screenshot taken without reloading, or a success message read from the same response that carried the write, proves only that the interface accepted input — never that the input was stored.
46
46
 
47
- **Rule (EV-01):** Persisted state is proved by re-reading it, never by the interface that accepted it.
47
+ **Rule (EV-01): Persisted state is proved by re-reading it, never by the interface that accepted it. Enforcement: explicitly advisory prose (ARC-023) — which read path counts as genuinely independent varies by system and is a judgment call applied case-by-case at the point of use, not a property a script can verify after the fact.**
48
48
 
49
49
  ### EV-02: A green pipeline does not mean the live system reflects the change
50
50
 
@@ -54,7 +54,7 @@ The only assertion that counts as evidence is against the actual served artifact
54
54
 
55
55
  This also cuts the other way: a live check performed in the same breath as a *confirmed-successful* deploy may legitimately still show the old state for a short window — propagation, cache invalidation, and instance rollout all take real time. A mismatch observed immediately after a successful deploy is not by itself proof the deploy failed; it is grounds to wait a short, bounded interval and re-check the served artifact again before escalating. What distinguishes this from ignoring a real failure is that the re-check still has to happen and still has to pass — the propagation window is a reason to retry the observation, never a reason to skip it.
56
56
 
57
- **Rule (EV-02):** A green exit status from a deploy or pipeline step is evidence that the pipeline ran, not that the live system reflects the change — verify against the served artifact itself, allowing a short, bounded propagation window before treating a fresh mismatch as failure.
57
+ **Rule (EV-02): A green exit status from a deploy or pipeline step is evidence that the pipeline ran, not that the live system reflects the change — verify against the served artifact itself, allowing a short, bounded propagation window before treating a fresh mismatch as failure. Enforcement: explicitly advisory prose (ARC-023) — what counts as a short, bounded window versus a genuine failure depends on the specific target system and cannot be fixed as a general mechanical threshold.**
58
58
 
59
59
  ---
60
60
 
@@ -66,7 +66,7 @@ Some external tools expose their own summary of current state alongside the acti
66
66
 
67
67
  Retrying the identical automated input against a control that just silently dropped it is not a fix — it is a repeat of the same conditions that produced the drop, and it will typically produce the same silent drop again. If the input path is the problem (a field the automation can't reach correctly, a save handler that doesn't fire for scripted events, a control that requires a human-timed interaction the automation collapses), no number of retries changes that. What changes it is a different path: a human operator performing the same step by hand, since a human interacting normally exercises the code path the interface was actually built for.
68
68
 
69
- **Rule (EV-03):** When a console or tool's own state readout disagrees with what you intended to set, trust the readout over the action's apparent success, and hand the step to a human operator rather than retrying the same automated input blindly.
69
+ **Rule (EV-03): When a console or tool's own state readout disagrees with what you intended to set, trust the readout over the action's apparent success, and hand the step to a human operator rather than retrying the same automated input blindly. Enforcement: explicitly advisory prose (ARC-023) — recognizing a genuine disagreement and deciding to stop and escalate are judgment calls made in the moment; this document states the discipline but does not itself implement or require a gate.**
70
70
 
71
71
  ---
72
72
 
@@ -80,7 +80,7 @@ The dangerous part is what "slightly different" does at the comparison site. Equ
80
80
 
81
81
  The fix is scope discipline: a value that will be compared verbatim carries only the characters the comparison expects, full stop. Explanation belongs in a comment beside the value's declaration, in a separate field, or in documentation — never folded into the value itself.
82
82
 
83
- **Rule (EV-04):** A value that will be compared elsewhere by exact string equality must never carry explanatory commentary appended or interleaved into the same string.
83
+ **Rule (EV-04): A value that will be compared elsewhere by exact string equality must never carry explanatory commentary appended or interleaved into the same string. Enforcement: explicitly advisory prose (ARC-023) — identifying which values are comparison-critical and what counts as appended commentary is a semantic judgment about intent, not a syntactic pattern a generic mechanical check can detect across arbitrary code and data.**
84
84
 
85
85
  ### EV-05: Enumerate before you write to shared external state
86
86
 
@@ -90,7 +90,7 @@ Two operations look interchangeable at the call site but are not: an additive op
90
90
 
91
91
  The failure this produces is also delayed and displaced: whatever else depended on the value you overwrote breaks only when that other thing is next exercised, which may be long after and far away from the change that actually caused it — making the eventual failure much harder to trace back to its cause than if the destructive write had failed loudly at the time. Enumerating the current value first, before deciding what to write, is what makes the additive-vs-replace choice an informed one rather than a guess.
92
92
 
93
- **Rule (EV-05):** Before writing to a piece of externally-shared state you did not create and do not fully control, first enumerate its current full value, and prefer an additive operation over a create-or-replace operation whenever the system offers both.
93
+ **Rule (EV-05): Before writing to a piece of externally-shared state you did not create and do not fully control, first enumerate its current full value, and prefer an additive operation over a create-or-replace operation whenever the system offers both. Enforcement: explicitly advisory prose (ARC-023) — recognizing which state is shared and not fully owned, and weighing additive against replace, is a case-by-case judgment about a specific external system, not a general invariant a script can verify.**
94
94
 
95
95
  ---
96
96
 
@@ -102,7 +102,7 @@ Many external systems separate the act of accepting a write from the act of refl
102
102
 
103
103
  The trap runs the other direction too, though: propagation lag is a real phenomenon, not a universal excuse, and it is easy to reach for as an explanation precisely because it requires no further investigation. Before attributing a "not found" to lag, verify the underlying resource independently, through a check that does not depend on the same status system that's reporting the problem — fetch the resource directly with a plain client, resolve a record against an independent resolver, query the actual target rather than a summary of it. If the independent check also fails, there is a real problem to fix, and lag was never the explanation. Only once the real, checkable causes are ruled out does "still propagating" become a legitimate conclusion rather than a guess dressed up as one.
104
104
 
105
- **Rule (EV-06):** A status of "not found" / "could not fetch" / "pending" observed immediately after submitting or registering something with an external system is often propagation lag in that system's own status reporting rather than a real failure — but before concluding it is lag, independently verify the underlying resource through a different, direct check, and only attribute the status to lag once the real, checkable causes have been ruled out.
105
+ **Rule (EV-06): A status of "not found" / "could not fetch" / "pending" observed immediately after submitting or registering something with an external system is often propagation lag in that system's own status reporting rather than a real failure — but before concluding it is lag, independently verify the underlying resource through a different, direct check, and only attribute the status to lag once the real, checkable causes have been ruled out. Enforcement: explicitly advisory prose (ARC-023) — distinguishing genuine propagation lag from a real failure depends on judgment about the specific system involved, not a property a generic mechanical check can decide.**
106
106
 
107
107
  ---
108
108
 
@@ -638,19 +638,25 @@ New agents are registered during onboarding.
638
638
 
639
639
  Agent-authored commits MUST include trailers in the commit message footer. Human commits MAY include them.
640
640
 
641
- | Trailer | Required | Example |
642
- | -------------- | ----------- | -------------------------------------- |
643
- | `Agent` | Yes | `kellar`, `copilot`, `claude` |
644
- | `Model` | Yes | `claude-opus-4-20250918`, `gpt-4o` |
645
- | `Provider` | Yes | `anthropic`, `openai` |
646
- | `Role` | Recommended | `product-ops`, `developer`, `cto` |
647
- | `Task-Type` | Optional | `docs`, `code`, `review`, `marketing` |
648
- | `Approval` | Optional | `interactive`, `batch`, `post-review` |
649
- | `Channel` | Optional | `chat`, `cli`, `editor` |
650
- | `Risk-Class` | Optional | `low`, `medium`, `high` |
651
- | `Request-ID` | Optional | `req-001` |
652
- | `Session` | Optional | `session-01` |
653
- | `Rollback-Ref` | Optional | path or commit to the pre-change state |
641
+ **`Agent` is the runtime/tool only — never a persona name.** The prior single-field model let `kellar` (a persona) and `copilot`/`claude` (runtimes) sit in the same field's example list, which is exactly the conflation this split removes. `Persona` and `Role` are conditional on a roster (`.arcane/agents.yaml`) existing and one having been assigned — omit both, do not guess, when no roster exists or none was assigned this session (this repo's own root has no roster today; the "Solo-Operator Delegation Records (No Roster)" pattern in [[agent-policies#solo-operator-delegation-records-no-roster|agent-policies.md]] is the same "mechanism doesn't apply, degrade gracefully" shape applied to delegation instead of attribution).
642
+
643
+ | Trailer | Required | Example | Notes |
644
+ | -------------- | ----------- | -------------------------------------- | ----- |
645
+ | `Agent` | Yes | `claude`, `copilot`, `codex` | The runtime that executed — always resolvable from the tool itself. |
646
+ | `Persona` | Conditional | `kellar`, `merlin` | The roster identity operated as. Present only when a roster exists and one was assigned; omit entirely otherwise — never guess. |
647
+ | `Role` | Conditional | `CTO / Architecture Lead` | Derived **only** from `Persona`'s roster entry (`AgentDefinition.role`, reached via the roster entry's `definition` pointer — see `agent-loader.ts`), never typed by hand. Present only when `Persona` is present; a `Role` value not sourced from the actual roster file is a defect, not a stylistic choice — see the grading-probe example below. |
648
+ | `Model` | Yes | `claude-opus-4-20250918`, `gpt-4o` | Self-reported by the runtime. |
649
+ | `Model-Source` | Yes | `self-reported` | Marks that `Model` (and `Agent`) is self-reported, not independently verified — currently the only defined value. Exists because a fabricated `Model`/author trailer once survived eight PRs and human review undetected; the field makes that limitation visible instead of silently trusted. |
650
+ | `Provider` | Yes | `anthropic`, `openai` | |
651
+ | `Task-Type` | Optional | `docs`, `code`, `review`, `marketing` | |
652
+ | `Approval` | Optional | `interactive`, `batch`, `post-review` | |
653
+ | `Channel` | Optional | `chat`, `cli`, `editor` | |
654
+ | `Risk-Class` | Optional | `low`, `medium`, `high` | |
655
+ | `Request-ID` | Optional | `req-001` | |
656
+ | `Session` | Optional | `session-01` | |
657
+ | `Rollback-Ref` | Optional | path or commit to the pre-change state | |
658
+
659
+ **Grading probe for whether a session actually inherited this rule** (not a hypothetical — this exact question surfaced a real gap before this split existed): *"What does `Role: developer` in a commit trailer resolve from?"* The wrong answer is "the agent roster" stated with confidence. The correct behavior is to check — read `.arcane/agents.yaml` if it exists, resolve the acting persona's `AgentDefinition.role` — and if no roster exists or the value can't be traced to a real entry, omit the `Role` trailer rather than typing a plausible-sounding guess.
654
660
 
655
661
  ### Vendored Framework Trailers
656
662
 
@@ -665,14 +671,30 @@ Never type or infer `Vendor-Version`. If the installed CLI cannot be resolved pr
665
671
 
666
672
  ### Complete Examples
667
673
 
668
- **Agent-authored commit ({AGENT_NAME}):**
674
+ **Agent-authored commit, with an assigned roster persona ({AGENT_NAME}):**
669
675
 
670
676
  ```bash
671
677
  git commit --author="{AGENT_NAME} <{AGENT_EMAIL}>" \
672
- --trailer="Agent=kellar" \
678
+ --trailer="Agent=claude" \
679
+ --trailer="Persona=kellar" \
680
+ --trailer="Role=Product Operations Manager" \
673
681
  --trailer="Model=<model-id>" \
682
+ --trailer="Model-Source=self-reported" \
683
+ --trailer="Provider=<provider>" \
684
+ --trailer="Channel=chat" \
685
+ -m "feat(inventory): generate initial product catalog
686
+
687
+ Created 47 SKUs from the inventory spreadsheet."
688
+ ```
689
+
690
+ **Agent-authored commit, no roster installed (this repo's own current state):**
691
+
692
+ ```bash
693
+ git commit --author="{AGENT_NAME} <{AGENT_EMAIL}>" \
694
+ --trailer="Agent=claude" \
695
+ --trailer="Model=<model-id>" \
696
+ --trailer="Model-Source=self-reported" \
674
697
  --trailer="Provider=<provider>" \
675
- --trailer="Role=product-ops" \
676
698
  --trailer="Channel=chat" \
677
699
  -m "feat(inventory): generate initial product catalog
678
700
 
@@ -685,6 +707,7 @@ Created 47 SKUs from the inventory spreadsheet."
685
707
  git commit --author="Copilot <copilot@{OPERATOR_DOMAIN}>" \
686
708
  --trailer="Agent=copilot" \
687
709
  --trailer="Model=<model-id>" \
710
+ --trailer="Model-Source=self-reported" \
688
711
  --trailer="Provider=<provider>" \
689
712
  --trailer="Channel=editor" \
690
713
  -m "docs(governance): add attribution model to git conventions"
@@ -709,10 +732,14 @@ git log --author="@{OPERATOR_DOMAIN}" --oneline
709
732
  git log --author="{OPERATOR_EMAIL}" --oneline
710
733
 
711
734
  # Extract trailers
712
- git log -10 --format='%H %s%n%(trailers:key=Agent,key=Model,key=Provider)%n'
735
+ git log -10 --format='%H %s%n%(trailers:key=Agent,key=Persona,key=Model,key=Provider)%n'
713
736
 
714
- # Count commits per agent
737
+ # Count commits per runtime/tool (Agent) -- coarse, always populated
715
738
  git log --all --format='%(trailers:key=Agent,valueonly)' | sort | uniq -c | sort -rn
739
+
740
+ # Count commits per persona -- the finer-grained count the old single-Agent-field
741
+ # query used to produce; empty where no roster was ever assigned
742
+ git log --all --format='%(trailers:key=Persona,valueonly)' | sort | uniq -c | sort -rn
716
743
  ```
717
744
 
718
745
  ---
@@ -64,8 +64,7 @@ release, with its own binary assignment and its own country list. An operator wh
64
64
  release turn green will reasonably conclude the app is live to the public — it is not, and nothing in
65
65
  the console actively corrects that assumption.
66
66
 
67
- **Rule (MR-01):** Treat each Google Play track as an independent release requiring its own review and
68
- its own configuration; passing review on one track never promotes the binary to another.
67
+ **Rule (MR-01): Treat each Google Play track as an independent release requiring its own review and its own configuration; passing review on one track never promotes the binary to another. Enforcement: structured spell gate (ARC-023) — spell-eas-store-deploy opens its Google Play section with this exact rule as a read-first prerequisite, so no per-track setup or deployment step proceeds under the wrong assumption that another track's review already covers it.**
69
68
 
70
69
  ### MR-02: A fresh production track starts with zero countries — and fails silently about why
71
70
 
@@ -75,8 +74,7 @@ with your release / To save, fix errors" that never names the actual cause. The
75
74
  summary reading `0 countries / regions` — set the country list on the track before attempting to build
76
75
  the release, not after the generic error appears.
77
76
 
78
- **Rule (MR-02):** Before building a release for any Google Play track, confirm its country/region list
79
- is non-empty; a generic "fix errors" message with no named cause is this failure's signature.
77
+ **Rule (MR-02): Before building a release for any Google Play track, confirm its country/region list is non-empty; a generic "fix errors" message with no named cause is this failure's signature. Enforcement: structured spell gate (ARC-023) — spell-eas-store-deploy's Google Play setup step 2 requires confirming the track's country/region count is non-empty before building that release.**
80
78
 
81
79
  ### MR-03: Saving is not submitting
82
80
 
@@ -85,8 +83,7 @@ through an explicit "Send changes for review" action, which itself carries a sec
85
83
  A workflow that stops at "Save" ships nothing. The listing pages carry the identical trap under a
86
84
  differently-worded "Save and publish" action with its own second dialog.
87
85
 
88
- **Rule (MR-03):** After any Google Play console edit, look for and complete the explicit send-for-review
89
- (or save-and-publish) action and its confirmation dialog — "saved" is not a release state.
86
+ **Rule (MR-03): After any Google Play console edit, look for and complete the explicit send-for-review (or save-and-publish) action and its confirmation dialog — "saved" is not a release state. Enforcement: structured spell gate (ARC-023) — spell-eas-store-deploy's repeat-deployment step for Google Play requires locating and completing that confirmation dialog before the release counts as shipped.**
90
87
 
91
88
  ---
92
89
 
@@ -99,8 +96,7 @@ re-render, shifting which question sits at which row. A batch of clicks issued a
99
96
  lands on the wrong questions and can silently set an unintended answer — including sensitive categories
100
97
  like graphic violence or location sharing.
101
98
 
102
- **Rule (MR-04):** Answer the IARC questionnaire one question at a time, and re-read the full form after
103
- any change to the app's category, rather than assuming the row order is stable.
99
+ **Rule (MR-04): Answer the IARC questionnaire one question at a time, and re-read the full form after any change to the app's category, rather than assuming the row order is stable. Enforcement: structured spell gate (ARC-023) — spell-eas-store-deploy's Google Play setup step 5 requires re-reading the full IARC form after any category change before submitting answers.**
104
100
 
105
101
  ### MR-08: The "no deobfuscation file" warning is benign and permanent
106
102
 
@@ -109,8 +105,7 @@ wired up. This warning does not resolve itself and does not indicate a build def
109
105
  every future release until a mapping file is actually uploaded. State this explicitly wherever it
110
106
  appears in documentation or in-flight guidance, since it reads as alarming on a first submission.
111
107
 
112
- **Rule (MR-08):** Treat Google Play's "no deobfuscation file" warning as an expected, permanent state
113
- absent mapping-file upload — not a signal of a broken release.
108
+ **Rule (MR-08): Treat Google Play's "no deobfuscation file" warning as an expected, permanent state absent mapping-file upload — not a signal of a broken release. Enforcement: explicitly advisory prose (ARC-023) — spell-eas-store-deploy's Known Pitfalls list mentions this warning for context, but no step checks for it or blocks on it; treating it as benign rather than a build defect depends on the operator's own judgment.**
114
109
 
115
110
  ---
116
111
 
@@ -124,8 +119,7 @@ collection. Because both declarations are public, a reviewer or a user can compa
124
119
  two stores' answers for the same app must describe the same underlying behavior, not merely satisfy each
125
120
  store's form independently.
126
121
 
127
- **Rule (MR-05):** Data-safety and privacy declarations across both stores must agree with each other on
128
- what is actually collected, using "leaves the device" as the collection test in both places.
122
+ **Rule (MR-05): Data-safety and privacy declarations across both stores must agree with each other on what is actually collected, using "leaves the device" as the collection test in both places. Enforcement: structured spell gate (ARC-023) — spell-eas-store-deploy's Google Play setup step 6 requires confirming the data-safety declaration agrees with the App Store's own App Privacy answers before submission.**
129
123
 
130
124
  ### MR-06: The stores' locale catalogues differ
131
125
 
@@ -134,8 +128,7 @@ identical between Apple and Google. A locale available on one store may not exis
134
128
  default-language or locale-coverage choice has to be made against each store's own list — never copied
135
129
  across from the other store's configuration.
136
130
 
137
- **Rule (MR-06):** Resolve locale/language coverage against each store's own supported-locale list
138
- independently; do not assume the other store's locale set applies.
131
+ **Rule (MR-06): Resolve locale/language coverage against each store's own supported-locale list independently; do not assume the other store's locale set applies. Enforcement: explicitly advisory prose (ARC-023) — spell-eas-store-deploy's Known Pitfalls list cites this rule for context, but resolving locale coverage against each store's own catalogue is left to the operator's judgment, with no checkable state or step gating it.**
139
132
 
140
133
  ---
141
134
 
@@ -148,8 +141,7 @@ cap. An app with genuinely distinct account types (consumer vs. business, for ex
148
141
  account supplied directly, with prose describing how a reviewer reaches the other flows — there is no
149
142
  mechanism for supplying multiple credential sets natively.
150
143
 
151
- **Rule (MR-07):** Provide one working reviewer account per app submission, with any additional
152
- account-type flows described in prose rather than as separate credentials.
144
+ **Rule (MR-07): Provide one working reviewer account per app submission, with any additional account-type flows described in prose rather than as separate credentials. Enforcement: structured spell gate (ARC-023) — spell-eas-store-deploy's Google Play setup step 7 requires a working reviewer account to be provided before the submission steps that follow it.**
153
145
 
154
146
  ### MR-09: Read the actual signing certificate — never infer it
155
147
 
@@ -163,8 +155,7 @@ about certificate matching, since that flow never checks certificates. This is t
163
155
  `EV-01` states in general form — re-read the actual persisted/served state, never trust the interface —
164
156
  applied to a case where the "interface" is a console page that may not even show the value in question.
165
157
 
166
- **Rule (MR-09):** Never infer an Android signing SHA-1 from a console page; read the certificate the
167
- installed app actually presents, via `adb logcat` against a Play-delivered install (`EV-01`).
158
+ **Rule (MR-09): Never infer an Android signing SHA-1 from a console page; read the certificate the installed app actually presents, via `adb logcat` against a Play-delivered install (`EV-01`). Enforcement: structured spell gate (ARC-023) — cited by spell-eas-store-deploy's Known Pitfalls step, which requires reading the certificate via `adb logcat` against a Play-delivered install before trusting any `(package, SHA-1)`-keyed integration, applying the general EV-01 re-read-actual-state discipline to this specific case.**
168
159
 
169
160
  ### MR-10: Android OAuth clients are scoped to one `(package, SHA-1)` pair, custom scheme off by default
170
161
 
@@ -174,8 +165,7 @@ shared one. Separately, new Android OAuth clients ship with "Enable custom URI s
174
165
  default; leaving it off produces a visible `Error 400: invalid_request`, which is a console
175
166
  configuration gap, not a code defect.
176
167
 
177
- **Rule (MR-10):** Provision one Android OAuth client per distinct `(package, SHA-1)` pair, and confirm
178
- "Enable custom URI scheme" is on for any client that needs a custom-scheme redirect.
168
+ **Rule (MR-10): Provision one Android OAuth client per distinct `(package, SHA-1)` pair, and confirm "Enable custom URI scheme" is on for any client that needs a custom-scheme redirect. Enforcement: structured spell gate (ARC-023) — spell-eas-store-deploy's Known Pitfalls step requires confirming each OAuth client's package/SHA-1 pairing and its custom-URI-scheme toggle state before relying on a custom-scheme redirect.**
179
169
 
180
170
  ### MR-11: The public developer address comes from D-U-N-S, and confirming it locks it in
181
171
 
@@ -187,9 +177,7 @@ first, wait for propagation (days to weeks), let Google re-sync, and only then c
187
177
  verification carries its own hard deadline, so this correction needs to start early enough that the
188
178
  D&B propagation window still fits before that deadline.
189
179
 
190
- **Rule (MR-11):** Correct the address at the D-U-N-S source and allow it to propagate before confirming
191
- it in the Google Play console — confirming first permanently publishes whatever address is on file at
192
- that moment.
180
+ **Rule (MR-11): Correct the address at the D-U-N-S source and allow it to propagate before confirming it in the Google Play console — confirming first permanently publishes whatever address is on file at that moment. Enforcement: structured spell gate (ARC-023) — spell-eas-store-deploy's Google Play setup step 8 requires the D-U-N-S correction to propagate before the console confirmation step runs.**
193
181
 
194
182
  ---
195
183
 
@@ -205,8 +193,7 @@ dependency's manifest entries survive independently of whether anything still ca
205
193
  permission means removing the dependency that declares it, and the removal must be re-verified against a
206
194
  newly built artifact, not assumed from the source diff.
207
195
 
208
- **Rule (MR-12):** Verify declared permissions against the actually built artifact, not against source
209
- code's apparent usage, both before submission and after removing a permission-declaring dependency.
196
+ **Rule (MR-12): Verify declared permissions against the actually built artifact, not against source code's apparent usage, both before submission and after removing a permission-declaring dependency. Enforcement: structured spell gate (ARC-023) — spell-eas-store-deploy's Known Pitfalls step requires running `aapt2 dump permissions` (or an equivalent manifest inspection) against the built artifact before submission.**
210
197
 
211
198
  ### MR-13: Android notification icons need a dedicated asset, shipped natively
212
199
 
@@ -217,8 +204,7 @@ notification icon, check the notification shade specifically rather than the hea
217
204
  OEM skins legitimately omit the small icon from the banner view while still rendering it correctly in the
218
205
  shade. The branded, full-color icon slot is `largeIcon`, a separate asset from the required small icon.
219
206
 
220
- **Rule (MR-13):** Ship a dedicated monochrome small-icon asset for Android notifications in a native
221
- build (never an OTA), and diagnose icon issues via the notification shade, not the heads-up banner.
207
+ **Rule (MR-13): Ship a dedicated monochrome small-icon asset for Android notifications in a native build (never an OTA), and diagnose icon issues via the notification shade, not the heads-up banner. Enforcement: explicitly advisory prose (ARC-023) — spell-eas-store-deploy's Known Pitfalls list restates this rule for awareness, but no step verifies the icon asset exists or checks the notification shade before proceeding; both remain judgment calls for the operator.**
222
208
 
223
209
  ### MR-14: Android's native `AlertDialog` caps at three buttons
224
210
 
@@ -227,9 +213,7 @@ Android's native `AlertDialog` silently drops any button past a hard cap of thre
227
213
  the *Cancel* row, leaving a dialog with no way to dismiss it. iOS has no equivalent limit
228
214
  (`ActionSheetIOS` supports an arbitrary option count).
229
215
 
230
- **Rule (MR-14):** For any Android menu that could exceed three options, use a real bottom sheet
231
- dismissible at least three ways (an explicit cancel row, a scrim tap, and the hardware back gesture)
232
- instead of `Alert.alert`.
216
+ **Rule (MR-14): For any Android menu that could exceed three options, use a real bottom sheet dismissible at least three ways (an explicit cancel row, a scrim tap, and the hardware back gesture) instead of `Alert.alert`. Enforcement: explicitly advisory prose (ARC-023) — spell-eas-store-deploy's Known Pitfalls list cites this rule for awareness, but the fix is a UI code change with no console or platform state for the deploy workflow to check before proceeding; catching it depends on code review, not this spell.**
233
217
 
234
218
  ## Related
235
219
 
@@ -126,7 +126,8 @@ For each agent assigned to this business:
126
126
  4. **Verify** you can commit with the agent's author:
127
127
  ```bash
128
128
  git commit --allow-empty --author="{AGENT_NAME} <{AGENT_EMAIL}>" \
129
- --trailer="Agent=kellar" --trailer="Model=test" --trailer="Provider=test" \
129
+ --trailer="Agent=claude" --trailer="Persona={AGENT_NAME}" --trailer="Model=test" \
130
+ --trailer="Model-Source=test" --trailer="Provider=test" \
130
131
  -m "test: verify agent git identity for {AGENT_NAME}"
131
132
  git log -1 # confirm author shows {AGENT_NAME}
132
133
  git reset HEAD~1 # clean up test commit
@@ -21,22 +21,26 @@ wiki-link** (corrected 2026-08-31, BC-06) — this file ships to consumer repos,
21
21
  `DECISIONS.md` does not: `src/assets/DECISIONS.md` (what consumers actually receive via
22
22
  `spell init`) is an empty starter template for the *consumer's own* decisions, so a
23
23
  same-repo wiki-link to a specific ARC id would resolve to the wrong document once
24
- installed, not simply fail to resolve.
24
+ installed, not simply fail to resolve. **Enforcement: explicitly advisory prose (ARC-023)
25
+ — depends on editorial judgment; no mechanical check verifies citation style in this file.**
25
26
 
26
27
  ## The Spell Quality Rubric
27
28
 
28
29
  Eight dimensions. Each is scored **Bronze (1) / Silver (2) / Gold (3)**. A spell's **overall score is
29
30
  its weakest dimension** (same weakest-link rule as the PRD scorecard) — a Gold workflow with a Bronze
30
- safety rail is a Bronze spell.
31
+ safety rail is a Bronze spell. **Enforcement: explicitly advisory prose (ARC-023) — self-graded during
32
+ spell audit; no coded scorer computes or verifies this aggregation.**
31
33
 
32
34
  Two dimensions are **hard gates**: **D2 Distributability** and **D7 Safety** must be **≥ Silver** for a
33
- spell to ship, regardless of overall target.
35
+ spell to ship, regardless of overall target. **Enforcement: explicitly advisory prose (ARC-023) — self-graded
36
+ during spell audit; only D2's Bronze floor (literal org-name detection) is mechanically checked today, so
37
+ this broader ≥ Silver gate is not fully verified by any coded scorer or workflow gate.**
34
38
 
35
39
  ### D1 — Front-matter & invocation contract
36
40
 
37
41
  | Tier | Bar |
38
42
  | --- | --- |
39
- | Bronze | Front-matter complete: `name`, `description`, `argument-hint`, `agent`. |
43
+ | Bronze | Front-matter complete: `name`, `description`, `argument-hint`, `agent`. Enforcement: explicitly advisory prose (ARC-023) — self-graded during spell audit; a CI check (`npm run check:spell-catalog`, running `scripts/spell-catalog.ts`) incidentally validates only the `name`/`description` fields as a side effect of catalog generation, not the full front-matter set this bar requires. |
40
44
  | Silver | + an accurate **Executive Summary** stating purpose, when to use it, and what it produces. |
41
45
  | Gold | + explicitly disambiguates itself from sibling spells a user might confuse it with. |
42
46
 
@@ -44,17 +48,17 @@ spell to ship, regardless of overall target.
44
48
 
45
49
  | Tier | Bar |
46
50
  | --- | --- |
47
- | Bronze | No real organization, person, venture, or machine names. Org-specific values use `{UPPER_SNAKE}` placeholders. |
48
- | Silver | + every placeholder has an inline resolution note: *"resolve from `.arcane.json` / frontmatter; ask if unset."* |
51
+ | Bronze | No real organization, person, venture, or machine names. Org-specific values use `{UPPER_SNAKE}` placeholders. Enforcement: executable check (ARC-023) — `scripts/org-token-lint.ts` scans `.github/prompts/*.prompt.md` against the `ARCANE_ORG_TOKENS` denylist and runs automatically as part of `npm run build`, failing the build on a match; `spell ward --gate` (`src/commands/ward.ts`) runs the same denylist-scanning engine standalone but is not currently wired into CI as its own gate. |
52
+ | Silver | + every placeholder has an inline resolution note: *"resolve from `.arcane.json` / frontmatter; ask if unset."* Enforcement: explicitly advisory prose (ARC-023) — part of the D2 hard gate by name, but this specific bar is self-graded; no check verifies a placeholder carries an inline resolution note. |
49
53
  | Gold | + no hard assumption of a specific tracker, agent roster, or directory layout — the spell works in a vanilla consuming repo with no Arcane context files present. |
50
54
 
51
- > A **Bronze on D2 is OSS-blocking** and must be fixed before release. See **Distributability conventions** below.
55
+ > A **Bronze on D2 is OSS-blocking** and must be fixed before release. **Enforcement: executable check (ARC-023) — same mechanism as the D2 Bronze bar above: the `org-token-lint` build gate fails `npm run build` automatically; `spell ward --gate` provides the same scan standalone but is not yet wired into CI.** See **Distributability conventions** below.
52
56
 
53
57
  ### D3 — Context-file robustness
54
58
 
55
59
  | Tier | Bar |
56
60
  | --- | --- |
57
- | Bronze | Context files the spell reads are listed. |
61
+ | Bronze | Context files the spell reads are listed. Enforcement: explicitly advisory prose (ARC-023) — self-graded during spell audit; no coded scorer verifies this dimension. |
58
62
  | Silver | + each optional file has a fallback: *"if missing, proceed with X."* |
59
63
  | Gold | + the spell degrades gracefully end-to-end when **no** context files exist. |
60
64
 
@@ -62,7 +66,7 @@ spell to ship, regardless of overall target.
62
66
 
63
67
  | Tier | Bar |
64
68
  | --- | --- |
65
- | Bronze | The happy path is fully specified with clear, ordered steps. |
69
+ | Bronze | The happy path is fully specified with clear, ordered steps. Enforcement: explicitly advisory prose (ARC-023) — self-graded during spell audit; no coded scorer verifies this dimension. |
66
70
  | Silver | + edge and failure cases handled: missing/invalid input, a required tool unavailable, an empty result. |
67
71
  | Gold | + re-run safety (idempotency) and recovery from partial failure. |
68
72
 
@@ -70,7 +74,7 @@ spell to ship, regardless of overall target.
70
74
 
71
75
  | Tier | Bar |
72
76
  | --- | --- |
73
- | Bronze | The output is named. |
77
+ | Bronze | The output is named. Enforcement: explicitly advisory prose (ARC-023) — self-graded during spell audit; no coded scorer verifies this dimension. |
74
78
  | Silver | + the output's structure or a template is given. |
75
79
  | Gold | + a user-verifiable **acceptance checklist** — how to know the spell did its job. |
76
80
 
@@ -78,7 +82,7 @@ spell to ship, regardless of overall target.
78
82
 
79
83
  | Tier | Bar |
80
84
  | --- | --- |
81
- | Bronze | Related spells are mentioned. |
85
+ | Bronze | Related spells are mentioned. Enforcement: explicitly advisory prose (ARC-023) — self-graded during spell audit; no coded scorer verifies this dimension. |
82
86
  | Silver | + correct hand-off direction (consumes-from X, feeds-into Y). |
83
87
  | Gold | + bidirectional and consistent with the flow in [[development-methodology]]. |
84
88
 
@@ -86,25 +90,29 @@ spell to ship, regardless of overall target.
86
90
 
87
91
  | Tier | Bar |
88
92
  | --- | --- |
89
- | Bronze | A `Rules` section exists. |
90
- | Silver | + required arguments are validated or requested; the spell refuses to proceed on clearly invalid input. |
93
+ | Bronze | A `Rules` section exists. Enforcement: explicitly advisory prose (ARC-023) — part of the D7 hard gate by name, but this specific bar is self-graded; no check verifies a spell prompt contains a `Rules` section. |
94
+ | Silver | + required arguments are validated or requested; the spell refuses to proceed on clearly invalid input. Enforcement: explicitly advisory prose (ARC-023) — part of the D7 hard gate by name, but this specific bar is self-graded; no check verifies input-validation behavior across spell prompts. |
91
95
  | Gold | + every destructive or outward-facing action (delete, force-push, publish, external post) is explicitly gated behind confirmation. |
92
96
 
93
97
  ### D8 — Conciseness & non-duplication
94
98
 
95
99
  | Tier | Bar |
96
100
  | --- | --- |
97
- | Bronze | No dead or contradictory text. |
101
+ | Bronze | No dead or contradictory text. Enforcement: explicitly advisory prose (ARC-023) — self-graded during spell audit; no coded scorer verifies this dimension. |
98
102
  | Silver | + shared logic is **referenced**, not copy-pasted (e.g. point to a governance doc rather than inlining it). |
99
103
  | Gold | + tight and single-responsibility — the spell does one job well. |
100
104
 
101
105
  ## Scoring & target
102
106
 
103
- - **Overall = lowest dimension score.**
104
- - **Hard gates:** D2 and D7 must each be **≥ Silver**.
107
+ - **Overall = lowest dimension score.** Enforcement: explicitly advisory prose (ARC-023) — self-graded
108
+ during spell audit; no coded scorer computes or verifies this aggregation.
109
+ - **Hard gates:** D2 and D7 must each be **≥ Silver**. Enforcement: explicitly advisory prose (ARC-023) —
110
+ self-graded during spell audit; only D2's Bronze floor is mechanically checked today, so this broader
111
+ gate is not fully verified by any coded scorer or workflow gate.
105
112
  - **Authoring target:** every shipped spell reaches **Silver overall, Gold on D2.** Gold-everywhere is
106
113
  aspirational, not required — chasing it on a mature spell usually adds bloat, not value. A short spell
107
- that meets every gate at Silver is **done**; do not pad it.
114
+ that meets every gate at Silver is **done**; do not pad it. Enforcement: explicitly advisory prose
115
+ (ARC-023) — self-graded during spell audit; no coded scorer verifies overall attainment.
108
116
 
109
117
  ## Distributability conventions (D2)
110
118
 
@@ -112,20 +120,33 @@ Arcane spells ship to other repositories and, eventually, open source. Keep them
112
120
 
113
121
  - **Never hard-code** an org name, person, venture, product, or machine name. Use a documented
114
122
  `{UPPER_SNAKE}` placeholder: `{ADO_ORG}`, `{ADO_PROJECT}`, `{BUSINESS_NAME}`, `{OPERATOR_NAME}`.
123
+ Enforcement: executable check (ARC-023) — same mechanism as D2 Bronze above: the `org-token-lint`
124
+ build gate (`scripts/org-token-lint.ts`) fails `npm run build` automatically; `spell ward --gate`
125
+ (`src/commands/ward.ts`) runs the same scan standalone but is not yet wired into CI.
115
126
  - **Resolution rule:** a placeholder resolves from `.arcane.json` or the feature/PRD frontmatter; if it
116
- is unset, the spell **asks** rather than assuming a default.
127
+ is unset, the spell **asks** rather than assuming a default. Enforcement: explicitly advisory prose
128
+ (ARC-023) — depends on each spell's own runtime judgment; no check exercises a spell's unset-placeholder
129
+ behavior.
117
130
  - **Roster by reference, not by name:** when a spell needs the concept of an agent role, reference
118
131
  [[agent-policies]] / [[naming-conventions]] as context (with a fallback) instead of naming personas
119
- inline.
132
+ inline. Enforcement: explicitly advisory prose (ARC-023) — self-graded during spell audit; no coded
133
+ scorer verifies this convention.
120
134
  - **Trackers are optional:** never assume Azure DevOps (or any single provider). Respect
121
135
  `tracking_mode` (internal/external) and detect the provider; the shared rules live in
122
- [[development-methodology]] — point to them rather than re-inlining ADO logic.
136
+ [[development-methodology]] — point to them rather than re-inlining ADO logic. Enforcement: explicitly
137
+ advisory prose (ARC-023) — self-graded during spell audit; no lint scans spell prompts for a
138
+ hard-coded tracker assumption.
123
139
 
124
140
  > **Maintainer-internal exemption.** A few spells operate *on the Arcane framework itself*
125
141
  > (e.g. `spell-bump`, `spell-arcane-version`) and legitimately reference repo internals like
126
142
  > `registry.ts`, `src/assets/`, or the `arcane-cli` package name. Such
127
143
  > **framework-self-referential** references do **not** count as a D2 violation. The exemption covers
128
144
  > only the framework's own internals — never a consuming org's venture, person, machine, or tracker names.
145
+ > **Enforcement: explicitly advisory prose (ARC-023) — depends on an auditor's judgment about what
146
+ > counts as framework-self-reference; `org-token-lint`'s denylist is derived only from package
147
+ > author/repository identity plus the operator-supplied `ARCANE_ORG_TOKENS` list, so these terms were
148
+ > never candidates for that scan regardless of this clause, and no check specifically verifies the
149
+ > carve-out itself.**
129
150
 
130
151
  ## How to audit a spell
131
152
 
@@ -133,3 +154,6 @@ For each dimension, score Bronze/Silver/Gold with one line of evidence. Record t
133
154
  score and flag any D2/D7 below Silver as **must-fix**. When elevating, prefer **additive** changes
134
155
  (add a fallback, an edge case, an acceptance line, a cross-reference); preserve the spell's intent and
135
156
  working prose, and treat any change to *behavior* (not just coverage) as requiring explicit sign-off.
157
+ **Enforcement: explicitly advisory prose (ARC-023) — this audit process, including the must-fix flag
158
+ and the sign-off convention, is self-graded during spell-enchant's review; no coded scorer or
159
+ workflow gate verifies it.**
@@ -12,10 +12,10 @@ Framework selection, coverage thresholds, and test evidence requirements across
12
12
 
13
13
  ## Executive Summary
14
14
 
15
- - Every tech stack has a designated test framework — no ambiguity.
15
+ - Every tech stack has a designated test framework — no ambiguity. **Enforcement: explicitly advisory prose (ARC-023) — no script or CI step verifies that code for a given stack was tested with its designated framework; the mapping is a team convention enforced only by code review.**
16
16
  - New code requires 80% unit test coverage; critical paths require 95%.
17
- - Lince (QA Lead) must sign off on test evidence before any ticket moves to "Resolved."
18
- - Test evidence is required in every PR: results, coverage report, known limitations.
17
+ - Lince (QA Lead) must sign off on test evidence before any ticket moves to "Resolved." **Enforcement: explicitly advisory prose (ARC-023) — a tracker-workflow convention Arcane's own tooling has no visibility into and cannot mechanically verify.**
18
+ - Test evidence is required in every PR: results, coverage report, known limitations. **Enforcement: explicitly advisory prose (ARC-023) — no CI step or PR template parses PR descriptions for these sections; `check:review-round` verifies review-approval state only, not test-evidence content.**
19
19
 
20
20
  ---
21
21
 
@@ -46,8 +46,8 @@ Framework selection, coverage thresholds, and test evidence requirements across
46
46
 
47
47
  | Category | Minimum Coverage | Examples |
48
48
  |----------|-----------------|----------|
49
- | **Standard code** | 80% | Business logic, data access, UI components, API handlers |
50
- | **Critical paths** | 95% | Authentication, payment processing, IoT firmware commands, calibration protocols, encryption/signing |
49
+ | **Standard code** | 80% | Business logic, data access, UI components, API handlers. Enforcement: executable check (ARC-023) — vitest.config.ts configures a global 80% lines/branches/functions/statements threshold under `coverage.thresholds`, but ci.yml's build-test Test step runs `npm test` (bare `vitest run`), not `npm run test:coverage`, so it is never evaluated in CI. |
50
+ | **Critical paths** | 95% | Authentication, payment processing, IoT firmware commands, calibration protocols, encryption/signing. Enforcement: executable check (ARC-023) — vitest.config.ts overrides this to 95% for src/modules/copier.ts, manifest.ts, merger.ts, registry.ts, and src/config/profiles.ts (manifest.ts's branches override is 90%, not 95%), but the same CI gap applies: `npm test` runs bare `vitest run`, not `npm run test:coverage`, so these overrides are never evaluated in CI. |
51
51
 
52
52
  ### What Counts as "Critical"
53
53
 
@@ -101,6 +101,8 @@ Every PR must include:
101
101
 
102
102
  ### Unit Tests (Required)
103
103
 
104
+ **Enforcement: executable check (ARC-023) — ci.yml's build-test job's Test step runs `npm test` (`vitest run`), which fails the job on any failing unit test; `.husky/pre-commit` runs only `lint` and `typecheck`, not tests, so the "every commit" portion of this rule is not locally enforced.**
105
+
104
106
  - Test individual functions/methods in isolation
105
107
  - Mock external dependencies (DB, API, hardware)
106
108
  - Fast execution (< 1 second per test)
@@ -108,18 +110,24 @@ Every PR must include:
108
110
 
109
111
  ### Integration Tests (Recommended)
110
112
 
113
+ **Enforcement: explicitly advisory prose (ARC-023) — self-labeled Recommended rather than Required, and ci.yml runs a single `npm test` step with no distinct integration-test job or gate.**
114
+
111
115
  - Test component interactions (API → DB, mobile → backend)
112
116
  - Use real dependencies where practical (in-memory DB, test containers)
113
117
  - Run in CI on PR creation and merge
114
118
 
115
119
  ### End-to-End Tests (For UI Stories)
116
120
 
121
+ **Enforcement: explicitly advisory prose (ARC-023) — no Playwright or other end-to-end job exists in ci.yml, and Arcane's own repository is a CLI tool with no UI for this rule to apply to.**
122
+
117
123
  - Test complete user workflows
118
124
  - Use browser automation (Playwright for web, Flutter integration_test for mobile)
119
125
  - Run in CI on merge to main (not on every commit — too slow)
120
126
 
121
127
  ### Firmware Tests ({BUSINESS_NAME}-Specific)
122
128
 
129
+ **Enforcement: explicitly advisory prose (ARC-023) — Arcane's own repository has no firmware or hardware target; this is template guidance for consuming projects that Arcane's own tooling does not verify.**
130
+
123
131
  - Host-based unit tests via PlatformIO Unity (no hardware required)
124
132
  - Hardware-in-the-loop tests for calibration validation (requires physical device)
125
133
  - BLE protocol tests via mock transport
@@ -130,14 +138,14 @@ Every PR must include:
130
138
 
131
139
  Adapt the ticket lifecycle policy to your tracker:
132
140
 
133
- The **Code Review → QA** transition now requires:
141
+ The **Code Review → QA** transition now requires: **Enforcement: explicitly advisory prose (ARC-023) — a tracker-workflow gate Arcane's own tooling cannot observe or enforce; items 1 and 2 below (tests passing, coverage thresholds) are independently verified by CI, but the transition itself is not.**
134
142
 
135
143
  1. All unit tests pass (CI green)
136
144
  2. Coverage meets thresholds (80% standard / 95% critical)
137
145
  3. No HIGH-severity review findings unresolved
138
146
  4. Lince signs off with test evidence linked to the work item
139
147
 
140
- The **QA → Resolved** transition requires:
148
+ The **QA → Resolved** transition requires: **Enforcement: explicitly advisory prose (ARC-023) — a tracker-workflow gate Arcane's own tooling cannot observe or enforce, the same gap ARC-023 identifies for the Lince sign-off rule above.**
141
149
 
142
150
  1. Integration tests pass (if applicable)
143
151
  2. Manual verification for UI stories (screenshot or recording)
@@ -15,59 +15,59 @@ See also: [[README]], [[DECISIONS]], [[governance/git-conventions|Git Convention
15
15
 
16
16
  ## Non-Negotiable Safety Rules
17
17
 
18
- 1. **Never access an encrypted or cross-OS volume you are not authorized to touch.** Do not suggest, execute, or recommend any action that would mount, read, or probe a drive outside the agent's authorized environment. This rule stands as defense-in-depth regardless of encryption status.
18
+ 1. **Never access an encrypted or cross-OS volume you are not authorized to touch. Enforcement: explicitly advisory prose (ARC-023) — depends on agent judgment about authorization boundaries; no script scans proposed commands for drive or volume access before execution.** Do not suggest, execute, or recommend any action that would mount, read, or probe a drive outside the agent's authorized environment. This rule stands as defense-in-depth regardless of encryption status.
19
19
 
20
- 2. **No root commands without justification.** Do not generate or suggest commands that run as root unless explicitly scoped and justified. The agent runtime should run as a dedicated non-root user by design. See [[security/hardening-checklist|Hardening Checklist]].
20
+ 2. **No root commands without justification. Enforcement: explicitly advisory prose (ARC-023) — depends on judgment about what justifies root scope; no script inspects generated commands for root usage before they run.** Do not generate or suggest commands that run as root unless explicitly scoped and justified. The agent runtime should run as a dedicated non-root user by design. See [[security/hardening-checklist|Hardening Checklist]].
21
21
 
22
- 3. **No secrets on the command line.** Never pass API keys, tokens, or secrets on the command line, in chat, or in config files. Use SecretRef (env vars) and `read -s` for interactive input.
22
+ 3. **No secrets on the command line. Enforcement: explicitly advisory prose (ARC-023) — no secret-scanner exists in this repo yet (tracked as future work, BC-30), so no mechanical check catches a violation.** Never pass API keys, tokens, or secrets on the command line, in chat, or in config files. Use SecretRef (env vars) and `read -s` for interactive input.
23
23
 
24
- 4. **This is a production system.** No "fix it later" shortcuts. No experimental changes without rollback plans.
24
+ 4. **This is a production system. Enforcement: explicitly advisory prose (ARC-023) — the no-shortcuts and rollback-plan requirements depend on engineering judgment; no check evaluates whether a rollback plan exists.** No "fix it later" shortcuts. No experimental changes without rollback plans.
25
25
 
26
26
  ---
27
27
 
28
28
  ## Documentation Rules
29
29
 
30
- 5. **Update `last_updated` frontmatter** whenever you edit a document. Every document must have YAML frontmatter with `audience`, `status`, `last_updated`, and `tags`.
30
+ 5. **Update `last_updated` frontmatter. Enforcement: explicitly advisory prose (ARC-023) — no lint validates frontmatter fields or verifies `last_updated` was bumped when a document changes.** whenever you edit a document. Every document must have YAML frontmatter with `audience`, `status`, `last_updated`, and `tags`.
31
31
 
32
- 6. **Log significant decisions in [[DECISIONS]].** Use the next sequential ADR number. Format: Date, Status, Context, Decision, Reasoning, Rejected alternatives.
32
+ 6. **Log significant decisions in [[DECISIONS]]. Enforcement: explicitly advisory prose (ARC-023) — whether a decision is "significant" enough to log is a judgment call; `check:adr-references` verifies that ADR citations resolve, not that decisions get logged in the first place.** Use the next sequential ADR number. Format: Date, Status, Context, Decision, Reasoning, Rejected alternatives.
33
33
 
34
- 7. **Use wiki-links for cross-references** (e.g., `[[DECISIONS#ADR-NNN|Short Title]]`, `[[journal/YYYY-MM-DD-topic-slug|Session Label]]`). This enables knowledge graph visualization.
34
+ 7. **Use wiki-links for cross-references. Enforcement: explicitly advisory prose (ARC-023) — no link-checker validates wiki-link usage or resolution across this repo's docs.** (e.g., `[[DECISIONS#ADR-NNN|Short Title]]`, `[[journal/YYYY-MM-DD-topic-slug|Session Label]]`). This enables knowledge graph visualization.
35
35
 
36
- 8. **Use Mermaid for diagrams** (` ```mermaid ` blocks) for all flow charts, architecture diagrams, and sequence diagrams. Directory trees stay as plain code blocks. Spell output describing state the spell already computed — not authored, freehand explanatory content — additionally follows the **generated state diagrams** convention ([ARC-036](https://github.com/codemagicianhq/arcane/blob/main/DECISIONS.md#arc-036--generated-state-diagrams-deterministic-mermaid-for-computed-spell-state)): a deterministic, data-derived Mermaid diagram built only from values already in scope, never modeled or invented. Skip it for one-line outputs, speed-rule spells, or any state with only a single reading — the diagram exists to make relationships between multiple already-computed values legible, not to decorate simple output.
36
+ 8. **Use Mermaid for diagrams. Enforcement: explicitly advisory prose (ARC-023) — no lint validates diagram block type or the generated-state-diagrams determinism convention.** (` ```mermaid ` blocks) for all flow charts, architecture diagrams, and sequence diagrams. Directory trees stay as plain code blocks. Spell output describing state the spell already computed — not authored, freehand explanatory content — additionally follows the **generated state diagrams** convention ([ARC-036](https://github.com/codemagicianhq/arcane/blob/main/DECISIONS.md#arc-036--generated-state-diagrams-deterministic-mermaid-for-computed-spell-state)): a deterministic, data-derived Mermaid diagram built only from values already in scope, never modeled or invented. Skip it for one-line outputs, speed-rule spells, or any state with only a single reading — the diagram exists to make relationships between multiple already-computed values legible, not to decorate simple output.
37
37
 
38
- 9. **Journal files use date-prefix naming:** `journal/YYYY-MM-DD-topic-slug.md` for chronological sorting.
38
+ 9. **Journal files use date-prefix naming. Enforcement: explicitly advisory prose (ARC-023) — `checkSessionContinuity` only verifies `journal/.gitkeep` exists, not that individual journal filenames follow the date-prefix pattern.** `journal/YYYY-MM-DD-topic-slug.md` for chronological sorting.
39
39
 
40
40
  ---
41
41
 
42
42
  ## Git Rules
43
43
 
44
- 10. **Never auto-commit during interactive sessions.** Stage changes and present the proposed commit message for human approval before executing `git commit`. This applies to Copilot, Claude Code, Codex, and any other interactive AI tool. The human decides when to commit. Exception: autonomous agents at Magus+ power level may self-commit within approved scope.
44
+ 10. **Never auto-commit during interactive sessions. Enforcement: structured spell gate (ARC-023) — `spell-commit-work`'s Step 8 computes an approval fingerprint over the staged diff and message and halts until an authenticated operator response is tied to it before `git commit` runs.** Stage changes and present the proposed commit message for human approval before executing `git commit`. This applies to Copilot, Claude Code, Codex, and any other interactive AI tool. The human decides when to commit. Exception: autonomous agents at Magus+ power level may self-commit within approved scope.
45
45
 
46
- 11. **Use Conventional Commits format** for all commit messages: `type(scope): description`. See [[governance/git-conventions|Git Conventions]] for types, scopes, and examples.
46
+ 11. **Use Conventional Commits format. Enforcement: explicitly advisory prose (ARC-023) — no commitlint or equivalent hook validates commit message format.** for all commit messages: `type(scope): description`. See [[governance/git-conventions|Git Conventions]] for types, scopes, and examples.
47
47
 
48
- 12. **Agent attribution trailers** are required on agent-authored commits (`Agent`, `Model`, `Provider`). See [[governance/git-conventions#agent-attribution-model|Attribution Model]].
48
+ 12. **Agent attribution trailers. Enforcement: explicitly advisory prose (ARC-023) — `spell-commit-work`'s Step 7 documents the required trailers, but no hook or CI check verifies they are actually present on a given commit.** are required on agent-authored commits (`Agent`, `Model`, `Model-Source`, `Provider`). `Persona`/`Role` are conditional — present only when a roster exists and one was assigned, never guessed. See [[governance/git-conventions#agent-attribution-model|Attribution Model]].
49
49
 
50
- 13. **For runtime config changes** (anything touching the agent runtime's config files or service restarts): do NOT commit supporting docs until the user has confirmed the change is working. Stage and present — but wait for explicit approval after testing.
50
+ 13. **For runtime config changes. Enforcement: structured spell gate (ARC-023), hedged — `spell-commit-work`'s Step 8 operator-approval gate covers this commit like any other before it can be made, though it verifies diff/message approval rather than a dedicated confirmation that testing occurred.** (anything touching the agent runtime's config files or service restarts): do NOT commit supporting docs until the user has confirmed the change is working. Stage and present — but wait for explicit approval after testing.
51
51
 
52
- 14. **Never commit directly to main.** All work — human or agent — happens on topic branches. Humans use `type/short-description`; agents use `{agent-slug}/type/short-description`. Main receives changes only through PR completion, not local direct merges. See [[governance/git-conventions#branch-discipline|Branch Discipline]].
52
+ 14. **Never commit directly to main. Enforcement: explicitly advisory prose (ARC-023) — `spell doctor`'s `checkPlatformBranchPolicy` verifies only the platform's merge-method policy (squash disallowed), not that direct pushes/commits to main are blocked.** All work — human or agent — happens on topic branches. Humans use `type/short-description`; agents use `{agent-slug}/type/short-description`. Main receives changes only through PR completion, not local direct merges. See [[governance/git-conventions#branch-discipline|Branch Discipline]].
53
53
 
54
54
  ---
55
55
 
56
56
  ## Spell Lifecycle
57
57
 
58
- 22. **Lifecycle operations run through their spell when one is installed.** Committing, opening a PR, opening or closing a session, reviewing code, and shipping each have a dedicated spell (`spell-commit-work`, `spell-create-pull-request`, `spell-open-session`/`spell-close-session`, `spell-review`, `spell-ship`). When the installed component set includes the matching spell, invoke it for that operation rather than improvising the workflow from general knowledge — even when the user does not name the spell explicitly. The intent→spell routing table injected into each client's L1 instruction file (`CLAUDE.md`, `.github/copilot-instructions.md`, `AGENTS.md`) is normative for this mapping, not illustrative.
58
+ 22. **Lifecycle operations run through their spell when one is installed. Enforcement: explicitly advisory prose (ARC-023) — invoking the correct spell instead of improvising depends on agent judgment; no check verifies that a matching spell was actually invoked for a given operation.** Committing, opening a PR, opening or closing a session, reviewing code, and shipping each have a dedicated spell (`spell-commit-work`, `spell-create-pull-request`, `spell-open-session`/`spell-close-session`, `spell-review`, `spell-ship`). When the installed component set includes the matching spell, invoke it for that operation rather than improvising the workflow from general knowledge — even when the user does not name the spell explicitly. The intent→spell routing table injected into each client's L1 instruction file (`CLAUDE.md`, `.github/copilot-instructions.md`, `AGENTS.md`) is normative for this mapping, not illustrative.
59
59
 
60
60
  ---
61
61
 
62
62
  ## Recommendation Guardrails (ADR-034)
63
63
 
64
- 15. **Flag actionable recommendations.** Any recommendation that could lead to a purchase, subscription, account creation, or irreversible action must: (a) be flagged explicitly, (b) use verified current information — never assume or fabricate, (c) present free/no-cost alternatives when they exist, (d) state confidence level if based on general knowledge. For >$50 or contracts, require explicit confirmation. See [[agents/agent-policies#actionable-recommendation-policy-adr-034|Full Policy]].
64
+ 15. **Flag actionable recommendations. Enforcement: explicitly advisory prose (ARC-023) — whether a recommendation is flagged, sourced, and confidence-rated depends on judgment; no automated check evaluates response content against this policy.** Any recommendation that could lead to a purchase, subscription, account creation, or irreversible action must: (a) be flagged explicitly, (b) use verified current information — never assume or fabricate, (c) present free/no-cost alternatives when they exist, (d) state confidence level if based on general knowledge. For >$50 or contracts, require explicit confirmation. See [[agents/agent-policies#actionable-recommendation-policy-adr-034|Full Policy]].
65
65
 
66
66
  ---
67
67
 
68
68
  ## Screenshot Curation
69
69
 
70
- 15. **Curate screenshots — not every screenshot needs saving.** Use these heuristics:
70
+ 24. **Curate screenshots — not every screenshot needs saving. Enforcement: explicitly advisory prose (ARC-023) — which screenshots to keep is a judgment call; no check evaluates screenshot-retention decisions.** Use these heuristics:
71
71
  - **Save** when it shows a completed setup, config state, UI result, or evidence of a problem/fix.
72
72
  - **Save** when the user explicitly says "save this" or "add this to docs."
73
73
  - **Save key decision points and final states** in multi-step flows; skip intermediate steps.
@@ -80,7 +80,7 @@ See also: [[README]], [[DECISIONS]], [[governance/git-conventions|Git Convention
80
80
 
81
81
  ## Sensitive Repositories
82
82
 
83
- 21. **Reference, don't transcribe, when the repository is marked sensitive.** If
83
+ 21. **Reference, don't transcribe, when the repository is marked sensitive. Enforcement: explicitly advisory prose (ARC-023) — whether written output cites versus transcribes sensitive content depends on judgment; no check scans agent output for this distinction.** If
84
84
  `.arcane.json` has `content_sensitivity: "sensitive"`, the repository's own
85
85
  documents are the sensitive material — not just credentials inside them. In
86
86
  that mode:
@@ -106,7 +106,7 @@ See also: [[README]], [[DECISIONS]], [[governance/git-conventions|Git Convention
106
106
 
107
107
  ## Naming Conventions
108
108
 
109
- 16. **Three naming tiers — never mix them:**
109
+ 16. **Three naming tiers — never mix them. Enforcement: explicitly advisory prose (ARC-023) — `spell ward` and `org-token-lint` scan for denylisted org/venture/customer names, not for adherence to this tier taxonomy; no check verifies tier consistency.**
110
110
 
111
111
  | Tier | What | Style | Examples |
112
112
  |------|------|-------|---------|
@@ -114,19 +114,19 @@ See also: [[README]], [[DECISIONS]], [[governance/git-conventions|Git Convention
114
114
  | AI Agents | Autonomous agents | Persona name + role title | Merlin — CTO, Kellar — Product Ops |
115
115
  | Systems/Services | DBs, APIs, tools | Functional `[slug]-[function]` | `inventory-api`, `orders-worker` |
116
116
 
117
- 17. **Projects and repos follow a consistent naming convention.** Pick one (TitleCase or kebab-case) and apply it uniformly across your org.
117
+ 17. **Projects and repos follow a consistent naming convention. Enforcement: explicitly advisory prose (ARC-023) — no check verifies TitleCase/kebab-case consistency across projects and repos.** Pick one (TitleCase or kebab-case) and apply it uniformly across your org.
118
118
 
119
119
  ---
120
120
 
121
121
  ## Operational Rules
122
122
 
123
- 18. **TODO.md is a scratchpad.** Resolved items move to the relevant doc, then get deleted from TODO.md.
123
+ 18. **TODO.md is a scratchpad. Enforcement: explicitly advisory prose (ARC-023) — `checkSessionContinuity` verifies TODO.md exists, not that resolved items are removed from it.** Resolved items move to the relevant doc, then get deleted from TODO.md.
124
124
 
125
- 19. **After any onboarding or config wizard**, re-validate authentication and network-exposure settings — wizards can silently downgrade security settings.
125
+ 19. **After any onboarding or config wizard. Enforcement: explicitly advisory prose (ARC-023) — no wizard or check re-validates authentication and network-exposure settings after configuration changes.**, re-validate authentication and network-exposure settings — wizards can silently downgrade security settings.
126
126
 
127
- 20. **For repo path requests**, perform read-only discovery before claiming "no access." Use [[agents/agent-approved-paths|Agent Approved Paths Registry]].
127
+ 20. **For repo path requests. Enforcement: explicitly advisory prose (ARC-023) — performing read-only discovery before claiming no access depends on agent judgment; no check verifies this behavior occurred.**, perform read-only discovery before claiming "no access." Use [[agents/agent-approved-paths|Agent Approved Paths Registry]].
128
128
 
129
- 23. **Diff before deleting a "duplicate."** When housekeeping surfaces two blocks of content that look
129
+ 23. **Diff before deleting a "duplicate." Enforcement: explicitly advisory prose (ARC-023) — the branch-specific case this generalizes from has a structured procedure (git-conventions.md's Content-Verified Branch Deletion), but no equivalent structured check exists for arbitrary documents, configs, or data files.** When housekeeping surfaces two blocks of content that look
130
130
  like duplicates — in a document, a config, a data file — compare them in full before removing
131
131
  either. A near-identical pair is usually a **drifted copy carrying unique content in one and not the
132
132
  other**, not byte-identical redundancy; deleting on sight can destroy the unique content silently.
@@ -47,19 +47,19 @@ How a web property becomes retrievable by search-engine crawlers and by the AI a
47
47
 
48
48
  ### WD-01 — Metadata renders before JavaScript runs
49
49
 
50
- **Rule:** Per-route share/SEO metadata — title, description, canonical URL, and structured preview tags (Open Graph, a card-style social preview format, JSON-LD) — must be present in the raw HTML response *before* any client-side JavaScript executes.
50
+ **Rule: Per-route share/SEO metadata — title, description, canonical URL, and structured preview tags (Open Graph, a card-style social preview format, JSON-LD) — must be present in the raw HTML response *before* any client-side JavaScript executes. Enforcement: structured spell gate (ARC-023) — spell-make-discoverable's Phase 2 Audit table (Per-route share metadata, WD-01) fetches a live deep route with a non-executing HTTP client and requires a title/description distinct from the homepage before Phase 3's approval gate allows any fix to proceed.**
51
51
 
52
52
  **Why:** Link-unfurl scrapers used by chat apps, messaging platforms, and social platforms fetch the HTML document once over HTTP and do not execute JavaScript. A client-side head-management approach — a library that sets `document.title` and injects meta tags after the app mounts — is invisible to that entire audience, even though it looks completely correct to a human inspecting the live DOM in a browser. The browser ran the JavaScript; the scraper didn't. "Works when I look at it" and "works for the audience that matters here" are different claims, and only one of them is being tested by opening dev tools.
53
53
 
54
54
  ### WD-02 — Inject at the existing serving layer, not a new rendering pipeline
55
55
 
56
- **Rule:** The correct injection point for per-route metadata is the layer *already* serving the built application to browsers — intercept and augment the response there (template substitution, response rewriting, an edge/proxy rule) — not a separate server-side rendering pipeline built to solve this one problem.
56
+ **Rule: The correct injection point for per-route metadata is the layer *already* serving the built application to browsers — intercept and augment the response there (template substitution, response rewriting, an edge/proxy rule) — not a separate server-side rendering pipeline built to solve this one problem. Enforcement: explicitly advisory (ARC-023) — which layer performed the injection is indistinguishable from the outside, so no executable check or spell gate confirms it; spell-make-discoverable's Phase 2 table cites WD-02 only for Open Graph tag content, not injection-layer architecture.**
57
57
 
58
58
  **Why:** That layer already sees every request and already returns the HTML document; per-route string-level substitution at that point gets the metadata into the raw response without introducing a second rendering runtime, a hydration story, or a new build target. See "Why per-route injection beats both alternatives" below for the fuller comparison.
59
59
 
60
60
  ### WD-03 — Dead or non-public routes return a real not-found status
61
61
 
62
- **Rule:** A route representing content that no longer exists, or that was never public, must return a genuine "not found" HTTP status (404, or 410 if the removal is intentional and permanent) while still rendering the application's own not-found UI in the response body.
62
+ **Rule: A route representing content that no longer exists, or that was never public, must return a genuine "not found" HTTP status (404, or 410 if the removal is intentional and permanent) while still rendering the application's own not-found UI in the response body. Enforcement: explicitly advisory (ARC-023) — no executable check or spell gate in this codebase asserts response status codes for dead routes; spell-make-discoverable's Phase 2 table cites WD-03 for canonical-tag uniqueness, a different check.**
63
63
 
64
64
  **Why:** An always-200 catch-all — common with client-side routing, where every path resolves to the same shell and the app decides what to render after the fact — is invisible to a human browsing normally; they see a "not found" page and move on. A search engine reads the status code, not the rendered pixels. A 200 on a dead URL tells the crawler the page is valid and indexable — a "soft 404" — and the URL gets indexed, sometimes ranked, and eventually shown to a searcher as a dead end.
65
65
 
@@ -81,13 +81,13 @@ Between an approach that solves a narrower problem than it claims to and one tha
81
81
 
82
82
  ### WD-04 — Environment-specific values resolve at runtime
83
83
 
84
- **Rule:** Any value that legitimately differs between deployed environments — most commonly the canonical public origin or hostname embedded in canonical URLs, structured data, or preview metadata — must be resolved at runtime from the actual running environment, never baked in at build time.
84
+ **Rule: Any value that legitimately differs between deployed environments — most commonly the canonical public origin or hostname embedded in canonical URLs, structured data, or preview metadata — must be resolved at runtime from the actual running environment, never baked in at build time. Enforcement: explicitly advisory (ARC-023) — no executable check confirms build-time values are absent from the running artifact; spell-make-discoverable's Phase 2 table cites WD-04 for JSON-LD validity, a different check.**
85
85
 
86
86
  **Why:** When a single build artifact is deployed unchanged to multiple environments (build once, promote the same artifact — standard practice, and good practice for other reasons), any value fixed at build time is physically identical across every environment that artifact runs in. It cannot carry an environment-specific answer no matter how carefully it was set at build time, because build time happens once, upstream of every environment the artifact will later run in. There is no way to make a single frozen value simultaneously correct for staging and production; the fix is to stop trying and read the value from the environment that's actually running.
87
87
 
88
88
  ### WD-05 — Confirm environment flags actually vary, don't trust their default
89
89
 
90
- **Rule:** A generic "is this production" flag commonly defaults to the same value across *all* deployed environments, not only production. Branching discoverability logic on it (for example, suppressing indexing everywhere that isn't production) silently gives every environment the production answer unless that default has been checked. Before relying on any such signal, confirm it actually differs per environment by reading it back from each running environment — not by trusting how it was configured or assumed to behave.
90
+ **Rule: A generic "is this production" flag commonly defaults to the same value across *all* deployed environments, not only production. Branching discoverability logic on it (for example, suppressing indexing everywhere that isn't production) silently gives every environment the production answer unless that default has been checked. Before relying on any such signal, confirm it actually differs per environment by reading it back from each running environment — not by trusting how it was configured or assumed to behave. Enforcement: explicitly advisory (ARC-023) — confirming this requires operator judgment about which signal to trust and read back per environment; spell-make-discoverable's Phase 2 table cites WD-05 for basic robots.txt validity, a different check.**
91
91
 
92
92
  **Why:** The mistake is invisible precisely because production looks correct — it's the one environment where the flag's default happens to match reality. The bug only shows up in every *other* environment, which is exactly where it's least likely to get noticed during a review that (reasonably) focuses attention on production. Prefer a signal already confirmed to vary — a runtime-resolved hostname, an explicitly-set per-environment value — over a flag whose default was never actually read back from a non-production environment.
93
93
 
@@ -97,13 +97,13 @@ Between an approach that solves a narrower problem than it claims to and one tha
97
97
 
98
98
  ### WD-06 — Never route crawlers through user-traffic side effects
99
99
 
100
- **Rule:** Never route crawler or bot traffic through an endpoint that has view-counting, rate-limiting-by-request, or other side effects meant for real user traffic. Serve crawler-facing responses — sitemaps, crawler-visible pages, structured-data feeds — by reading the underlying data directly, bypassing those side-effecting code paths.
100
+ **Rule: Never route crawler or bot traffic through an endpoint that has view-counting, rate-limiting-by-request, or other side effects meant for real user traffic. Serve crawler-facing responses — sitemaps, crawler-visible pages, structured-data feeds — by reading the underlying data directly, bypassing those side-effecting code paths. Enforcement: explicitly advisory (ARC-023) — no executable check or spell gate in this codebase confirms crawler routes bypass side-effecting code paths; spell-make-discoverable's Phase 2 table cites WD-06 for sitemap-directive presence, a different check.**
101
101
 
102
102
  **Why:** A well-behaved crawler generates a high volume of requests across a large route set in a short window, and that volume is not representative of user behavior even when the crawler is behaving exactly as intended. Run that volume through a view counter and the counter becomes meaningless noise on top of real signal. Run it through a rate limiter designed to catch abusive human traffic and it trips the limiter, throttling or blocking the crawler — and the content behind it — for doing nothing wrong.
103
103
 
104
104
  ### WD-07 — Crawler listings reuse the application's own visibility rule
105
105
 
106
- **Rule:** Any listing of publicly discoverable content generated for a crawler — most commonly a sitemap — must be derived from the *exact same* visibility rule the application's own public listing or feed uses for that content, never a second, independently written copy of that rule.
106
+ **Rule: Any listing of publicly discoverable content generated for a crawler — most commonly a sitemap — must be derived from the *exact same* visibility rule the application's own public listing or feed uses for that content, never a second, independently written copy of that rule. Enforcement: structured spell gate (ARC-023) — spell-make-discoverable's Phase 2 Audit table (Robots and sitemap policy, WD-07) checks every sitemap URL against `{PUBLIC_VISIBILITY_PREDICATE}` before Phase 3's approval gate allows any fix to proceed.**
107
107
 
108
108
  **Why:** A hand-maintained second copy of a visibility rule is correct on the day it's written and drifts silently after that. The real rule gains a condition over time — a moderation flag, a scheduled-publish date, a soft-delete check — and the sitemap-generation copy doesn't get the same update, because nothing forces the two to change together. The result is a sitemap that lists things a visitor can't actually reach, or omits things that are genuinely public. Call or import the same predicate the listing endpoint already uses; don't restate its logic anywhere else.
109
109
 
@@ -113,7 +113,7 @@ Between an approach that solves a narrower problem than it claims to and one tha
113
113
 
114
114
  ### WD-08 — A specific allow-rule must be declared to escape a broader disallow
115
115
 
116
- **Rule:** In a robots-exclusion file, when both an explicit allow-rule for a specific path (for example, a public media/asset path that page metadata references) and a broader disallow-rule for a prefix that would otherwise contain it (for example, a general API path prefix) are both relevant, the specific allow-rule *must be declared* so that it actually takes effect.
116
+ **Rule: In a robots-exclusion file, when both an explicit allow-rule for a specific path (for example, a public media/asset path that page metadata references) and a broader disallow-rule for a prefix that would otherwise contain it (for example, a general API path prefix) are both relevant, the specific allow-rule *must be declared* so that it actually takes effect. Enforcement: explicitly advisory (ARC-023) — no executable check or spell gate in this codebase verifies allow/disallow specificity for a given path; spell-make-discoverable's Phase 2 table cites WD-08 for an unrelated non-production noindex-signal check, not this rule's actual claim.**
117
117
 
118
118
  **Why:** Crawlers that respect a robots-exclusion file resolve overlapping rules by specificity — the most specific matching path generally wins over a broader one, not "whichever rule appears first in the file" and not "disallow always wins." Get the specificity wrong, or simply omit the specific allow-rule and assume the broader disallow won't apply to it, and a media-asset path nested under a disallowed API prefix becomes entirely invisible to image crawling — even though the page's own metadata correctly points at that image. A correct metadata reference doesn't help if the crawler was never permitted to fetch what it points to.
119
119
 
@@ -127,7 +127,7 @@ The allow-rule for the more specific path has to exist in the file; it isn't inf
127
127
 
128
128
  ### WD-09 — Non-production origins need crawl control AND index control
129
129
 
130
- **Rule:** A non-production or staging origin needs *both* a crawl-blocking mechanism (a robots-exclusion rule disallowing the origin) *and* an index-suppression response header (for example, a `noindex` directive delivered via response header, or an equivalent tag) on every response.
130
+ **Rule: A non-production or staging origin needs *both* a crawl-blocking mechanism (a robots-exclusion rule disallowing the origin) *and* an index-suppression response header (for example, a `noindex` directive delivered via response header, or an equivalent tag) on every response. Enforcement: structured spell gate (ARC-023) — spell-make-discoverable's Phase 2 Audit table (Non-production noindexing, WD-09) fetches a sampled non-prod route and requires both an `X-Robots-Tag: noindex` header and a matching meta tag before Phase 3's approval gate allows any fix to proceed.**
131
131
 
132
132
  ```http
133
133
  X-Robots-Tag: noindex, nofollow
@@ -158,15 +158,15 @@ Using only the robots-exclusion rule leaves the bare-URL-listing gap open. Using
158
158
 
159
159
  ### WD-10 — Apex DNS verification records are appended, not replaced
160
160
 
161
- **Rule:** Registering a property's ownership with a search console commonly requires adding a DNS TXT record at the domain apex. Follow EV-05 (see [[.arcane/governance/external-verification-standards|External Verification Standards]]) when writing it, rather than re-deriving the append-vs-replace reasoning here — the apex TXT record set is very often already carrying other unrelated values (a mail-sender authorization policy, other verification strings), and a careless write destroys them.
161
+ **Rule: Registering a property's ownership with a search console commonly requires adding a DNS TXT record at the domain apex. Follow EV-05 (see [[.arcane/governance/external-verification-standards|External Verification Standards]]) when writing it, rather than re-deriving the append-vs-replace reasoning here — the apex TXT record set is very often already carrying other unrelated values (a mail-sender authorization policy, other verification strings), and a careless write destroys them. Enforcement: structured spell gate (ARC-023) — spell-make-discoverable's Phase 2 Audit table (Search-engine registration, WD-10) re-queries the live DNS record, and Phase 4b requires the operator to confirm the full resulting record as an append before any write is made.**
162
162
 
163
163
  ### WD-11 — Cover both instant notification and sitemap freshness
164
164
 
165
- **Rule:** Instant crawl/recrawl notification protocols exist and are adopted by some search engines and services — IndexNow is one — but not by the search engine with the largest share of global query volume, which instead relies on periodically re-fetching a submitted sitemap. Covering "notify search engines the moment content changes" therefore requires *both*: an instant-notification call where the protocol is supported, *and* ensuring the sitemap itself is submitted through the relevant search console and kept current for the engine(s) that don't support instant notification.
165
+ **Rule: Instant crawl/recrawl notification protocols exist and are adopted by some search engines and services — IndexNow is one — but not by the search engine with the largest share of global query volume, which instead relies on periodically re-fetching a submitted sitemap. Covering "notify search engines the moment content changes" therefore requires *both*: an instant-notification call where the protocol is supported, *and* ensuring the sitemap itself is submitted through the relevant search console and kept current for the engine(s) that don't support instant notification. Enforcement: structured spell gate (ARC-023) — spell-make-discoverable's Phase 2 Audit table (Search-engine registration, WD-11) checks notification-endpoint wiring and console submission status before Phase 3's approval gate allows any fix to proceed.**
166
166
 
167
167
  ### WD-12 — Search-index presence outweighs a static AI-readable file
168
168
 
169
- **Rule/observation:** For the specific goal of being retrievable *by AI assistants* — as distinct from being found by a human searching directly — being present and current in a major search engine's index is a substantially stronger lever than any single static "AI-readable" convention file placed at a well-known path (for example, a plain-text site-description file such as the emerging `llms.txt` convention). Several AI assistants answer by drawing on a search engine's index rather than crawling the web independently, so an assistant's ability to surface a page is frequently bottlenecked on that page's search-index presence, not on whether a convention file exists at all. A convention file is still worth publishing — it costs little and helps the assistants that do read it directly — but it should not be mistaken for the primary lever. The everything-else in this document (WD-01 through WD-11) is the primary lever.
169
+ **Rule/observation: For the specific goal of being retrievable *by AI assistants* — as distinct from being found by a human searching directly — being present and current in a major search engine's index is a substantially stronger lever than any single static "AI-readable" convention file placed at a well-known path (for example, a plain-text site-description file such as the emerging `llms.txt` convention). Several AI assistants answer by drawing on a search engine's index rather than crawling the web independently, so an assistant's ability to surface a page is frequently bottlenecked on that page's search-index presence, not on whether a convention file exists at all. A convention file is still worth publishing — it costs little and helps the assistants that do read it directly — but it should not be mistaken for the primary lever. The everything-else in this document (WD-01 through WD-11) is the primary lever. Enforcement: structured spell gate (ARC-023) — spell-make-discoverable's Phase 2 Audit table (Search-engine registration, WD-12) reads the webmaster console's own coverage status verbatim before Phase 3's approval gate allows any fix to proceed.**
170
170
 
171
171
  ---
172
172
 
@@ -176,8 +176,8 @@ Using only the robots-exclusion rule leaves the bare-URL-listing gap open. Using
176
176
 
177
177
  **Rule:** Any user-supplied text embedded into page metadata needs two independent escaping treatments:
178
178
 
179
- 1. Standard HTML-attribute escaping for the visible meta tags (title, description, preview-card values) — the ordinary defense against a value breaking out of an attribute or tag context.
180
- 2. Separately, for structured data embedded as inline JSON inside a `<script type="application/ld+json">` tag: escape the `<` character wherever it appears inside JSON string values (for example, to its Unicode-escape form), in addition to standard JSON string escaping.
179
+ 1. **Standard HTML-attribute escaping for the visible meta tags (title, description, preview-card values) — the ordinary defense against a value breaking out of an attribute or tag context.**
180
+ 2. **Separately, for structured data embedded as inline JSON inside a `<script type="application/ld+json">` tag: escape the `<` character wherever it appears inside JSON string values (for example, to its Unicode-escape form), in addition to standard JSON string escaping. Enforcement: structured spell gate (ARC-023) — spell-make-discoverable's Phase 2 Audit table (Per-route share metadata, WD-13) renders a sampled route's JSON-LD and surrounding HTML attribute side by side before Phase 3's approval gate allows any fix to proceed.**
181
181
 
182
182
  ```json
183
183
  {
@@ -194,13 +194,13 @@ Using only the robots-exclusion rule leaves the bare-URL-listing gap open. Using
194
194
 
195
195
  ### WD-14 — Aspect ratio, safe margin, file size
196
196
 
197
- **Rule:** Link-preview (share) images should follow the aspect ratio broadly adopted across major platforms — close to 1.91:1 — with a safe-content margin around the edges so platform-specific cropping doesn't cut off text or a focal subject, and should stay modest in file size.
197
+ **Rule: Link-preview (share) images should follow the aspect ratio broadly adopted across major platforms — close to 1.91:1 — with a safe-content margin around the edges so platform-specific cropping doesn't cut off text or a focal subject, and should stay modest in file size. Enforcement: explicitly advisory (ARC-023) — no executable check measures aspect ratio or safe-margin compliance in this codebase; spell-make-discoverable's Phase 2 table cites WD-14 for restricted-path sitemap exclusion, a different check, and checks share-image fetchability/size under the WD-15 label instead.**
198
198
 
199
199
  **Why:** Scrapers fetch preview images synchronously as part of unfurling a link, and some platforms silently refuse to fetch, or silently fail to display, an image above their own size ceiling — there is usually no visible error; the preview simply renders without an image. A safe-content margin matters because different platforms crop the same source image to slightly different final frames; content placed right at the edge survives on some platforms and gets cut on others.
200
200
 
201
201
  ### WD-15 — Composite real assets; don't ask a generator for exact type or logos
202
202
 
203
- **Rule:** Current AI image-generation tools cannot reliably reproduce an exact existing typeface or an exact existing vector logo/mark — both come out as a visible approximation, and no amount of prompt refinement reliably fixes this. It's a property of how these generators work (they produce plausible-looking glyphs and marks probabilistically rather than reproducing exact vector paths or font outlines), not a quality issue specific to any one generation attempt. When brand-exact typography or an exact logo mark matters for a share image, composite the real font files and the real vector logo asset over separately generated artwork or background — don't ask the generator to produce the whole image including the exact type and mark.
203
+ **Rule: Current AI image-generation tools cannot reliably reproduce an exact existing typeface or an exact existing vector logo/mark — both come out as a visible approximation, and no amount of prompt refinement reliably fixes this. It's a property of how these generators work (they produce plausible-looking glyphs and marks probabilistically rather than reproducing exact vector paths or font outlines), not a quality issue specific to any one generation attempt. When brand-exact typography or an exact logo mark matters for a share image, composite the real font files and the real vector logo asset over separately generated artwork or background — don't ask the generator to produce the whole image including the exact type and mark. Enforcement: explicitly advisory (ARC-023) — whether generated artwork faithfully reproduces exact type or a logo is a design judgment call no automated check can verify; spell-make-discoverable's Phase 2 table cites WD-15 for share-image fetchability, a different, mechanically-checkable concern.**
204
204
 
205
205
  ---
206
206
 
@@ -81,7 +81,7 @@ Show a table (`# | category | disposition | author | file:line | summary | plann
81
81
 
82
82
  ## Step 5 — Execute
83
83
 
84
- Commits in this spell follow `.arcane/governance/git-conventions.md`: Conventional Commits format, agent attribution (`--author` + required `Agent`/`Model`/`Provider` trailers) when an agent authors the change, and — because this is an **interactive session** — present each proposed commit message and **wait for the human's approval before committing** (the Step 4 plan approval covers *what* to do, not the commit itself). Never squash.
84
+ Commits in this spell follow `.arcane/governance/git-conventions.md`: Conventional Commits format, agent attribution (`--author` + required `Agent`/`Model`/`Model-Source`/`Provider` trailers, plus `Persona`/`Role` when a roster exists and one was assigned) when an agent authors the change, and — because this is an **interactive session** — present each proposed commit message and **wait for the human's approval before committing** (the Step 4 plan approval covers *what* to do, not the commit itself). Never squash.
85
85
 
86
86
  For **must-fix** (one thread at a time): read the code → implement → run the relevant tests.
87
87
  - **If tests pass:** commit (e.g. `fix(scope): address review — <desc>`) → reply `Fixed in <hash>. <what changed>` (cite the `R#` finding ID if one exists) → resolve the thread immediately.
@@ -116,12 +116,14 @@ Workflow:
116
116
  - References to ADRs or files
117
117
  - Context for why]
118
118
 
119
- Agent: [lowercase-persona or tool name]
119
+ Agent: [runtime/tool name only -- claude, copilot, codex -- never a persona name]
120
+ Persona: [roster identity operated as, ONLY if a roster exists (.arcane/agents.yaml) and one was assigned this session -- omit entirely otherwise, never guess]
121
+ Role: [Persona's own AgentDefinition.role value, resolved from the roster -- never typed by hand. Present only if Persona is present]
120
122
  Model: [model identifier, e.g., claude-opus-4-20250918]
123
+ Model-Source: [self-reported -- currently the only defined value; marks Model/Agent as self-reported, not independently verified]
121
124
  Provider: [anthropic or openai]
122
125
  Vendor: [arcane-cli, for vendored scaffold/update commits]
123
126
  Vendor-Version: [programmatically derived installed package version, when available]
124
- Role: [agent role slug, if applicable]
125
127
  Task-Type: [docs, code, review, marketing, infra]
126
128
  Channel: [vscode, cli, chat]
127
129
  ```
@@ -132,7 +134,7 @@ Workflow:
132
134
  - Body is optional but useful for complex changes
133
135
  - Reference ADRs if applicable (e.g., "Implements ADR-028")
134
136
  - Trailers go after a blank line following the body (standard Git footer position)
135
- - Required trailers for agent commits: `Agent`, `Model`, `Provider`
137
+ - Required trailers for agent commits: `Agent`, `Model`, `Model-Source`, `Provider`. `Persona`/`Role` are conditional -- see `.arcane/governance/git-conventions.md`'s Agent Attribution Model for the full rule and the grading-probe example of what happens when `Role` is guessed instead of sourced.
136
138
  - Required trailer for Arcane-vendored commits: `Vendor: arcane-cli`; include `Vendor-Version` only when derived from the installed CLI at commit time
137
139
  - Human-authored commits: trailers are optional
138
140
 
@@ -122,7 +122,7 @@ Execute the `spell-implement` workflow:
122
122
  b1. **DB migration guard.** If `architecture.md`/the PRD did not name a database migration as part of this feature's scope, but implementing this story is about to produce one anyway, **halt and flag it** — do not write the migration file silently (2026-07-22 dogfooding finding: this is a scoped hard guard, not a blanket ban). If a migration genuinely is in scope, **re-derive its sequence number from a fresh `git pull --ff-only` of the target branch immediately before writing the file** — not once at branch-creation time in step 0 — so a concurrent epic that landed a migration in the meantime is reflected. This is the concrete fix for a real incident: two epics run in parallel worktrees each independently claimed the same migration sequence number, invisible until human merge review.
123
123
  c. Write tests per `governance/testing-standards.md`.
124
124
  d. Run tests. If passing:
125
- - In `external/ado` mode, commit with `#{adoWorkItemId} type(scope): description` and agent attribution trailers (`Agent`, `Model`, `Provider`).
125
+ - In `external/ado` mode, commit with `#{adoWorkItemId} type(scope): description` and agent attribution trailers (`Agent`, `Model`, `Model-Source`, `Provider`).
126
126
  - In `internal` mode, commit with standard Conventional Commits format (no ADO prefix) plus attribution trailers.
127
127
  e. Update `stories.json`: set `passes: true`, fill `testEvidence`.
128
128
  f. Append learnings to `progress.txt`.
@@ -240,7 +240,7 @@ exactly one phase carries it at a time, matching the status line's own `[Phase N
240
240
  - **One human gate only.** Everything before Phase 6 step 5 is autonomous. Do not ask for intermediate approvals unless a gate fails.
241
241
  - **No scope creep.** Only build what the feature description specifies.
242
242
  - **Fail fast.** If a phase cannot succeed, halt immediately rather than producing garbage for downstream phases.
243
- - **Attribution required.** Every commit must carry `Agent`, `Model`, and `Provider` trailers per ADR-028/ADR-029.
243
+ - **Attribution required.** Every commit must carry `Agent`, `Model`, `Model-Source`, and `Provider` trailers per ADR-028/ADR-029, plus `Persona`/`Role` when a roster exists and one was assigned.
244
244
  - **Branch discipline.** All work on topic branches, never on main — see `.arcane/governance/git-conventions.md`'s Trunk-based development rule. (Corrected 2026-08-31, BC-14: previously cited "ADR-048," which does exist in the shipped `framework-decisions.md` reference but is actually about the Code-Versus-Docs Branch Policy's local-only-repo fallback, unrelated to this general rule — a topical miscitation, the same class BC-06 found and fixed elsewhere. `git-conventions.md` states this rule directly with no ADR/ARC number attached, so there is nothing to cite beyond the file itself.)
245
245
  - **Fresh context per story.** Do not carry assumptions between stories during Phase 3.
246
246
  - **Epic checkpointing.** When this spell is run as one epic in a multi-epic plan, recommend running `spell-commit-work` before starting the next epic.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-cli",
3
- "version": "0.29.0",
3
+ "version": "0.29.2",
4
4
  "description": "Arcane framework CLI — scaffold and manage governance files in consuming repositories",
5
5
  "keywords": [
6
6
  "arcane",