arcane-cli 0.32.0 → 0.32.3
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.
- package/dist/assets/.arcane/governance/cicd-standards.md +141 -89
- package/dist/assets/.arcane/governance/development-methodology.md +5 -5
- package/dist/assets/.arcane/governance/portable-bootstrap.md +1 -1
- package/dist/assets/.arcane/governance/spell-authoring-standards.md +13 -1
- package/dist/assets/.arcane/governance/web-discoverability-standards.md +8 -8
- package/dist/assets/.github/prompts/spell-make-discoverable.prompt.md +14 -9
- package/package.json +1 -1
|
@@ -8,19 +8,119 @@ tags: [cicd, pipelines, devops, prospero, azure-devops]
|
|
|
8
8
|
|
|
9
9
|
# CI/CD Standards
|
|
10
10
|
|
|
11
|
-
Pipeline patterns, branch policies, and deployment gates for all your projects.
|
|
11
|
+
Pipeline patterns, branch policies, and deployment gates for all your projects.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
**Structure (ARC-038 decision 2):** a vendor-neutral core (principles that apply regardless of
|
|
14
|
+
which CI/CD platform a repository uses) plus a provider-specific profile — today, Azure DevOps,
|
|
15
|
+
the only platform this document details in full. This reuses the same shape
|
|
16
|
+
[[development-methodology|development-methodology.md]] already uses successfully for tracking
|
|
17
|
+
providers (Process-Template-Aware ADO Hierarchy Rules alongside GitHub Issues Conventions), not a
|
|
18
|
+
new architecture. A future GitHub-Actions- or GitLab-CI-specific profile can be added the same way,
|
|
19
|
+
without touching the core.
|
|
14
20
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Core Principles (vendor-neutral)
|
|
24
|
+
|
|
25
|
+
Every code repository must have a CI pipeline that runs on PR creation and merge to main.
|
|
26
|
+
**Enforcement: explicitly advisory prose (ARC-023) — prescriptive guidance; Arcane's own tooling
|
|
27
|
+
does not enumerate an organization's repositories to verify each one has a CI pipeline.**
|
|
28
|
+
|
|
29
|
+
### Branch Policy Principles
|
|
30
|
+
|
|
31
|
+
These apply to any platform's branch-protection mechanism (GitHub branch rulesets, Azure DevOps
|
|
32
|
+
branch policies, GitLab merge-request approval rules, ...) — the Azure DevOps Profile below shows
|
|
33
|
+
one concrete platform's implementation of the same table.
|
|
34
|
+
|
|
35
|
+
| Policy | Setting | Rationale |
|
|
36
|
+
|--------|---------|-----------|
|
|
37
|
+
| **Require PR** | All branches → main | No direct pushes to main. |
|
|
38
|
+
| **Minimum reviewers** | At least 1 | Quality gate. |
|
|
39
|
+
| **Build validation** | CI pipeline must pass | No merging broken code. |
|
|
40
|
+
| **Work item / issue linking** | Required (when ticketing is active) | Traceability. |
|
|
41
|
+
| **Comment resolution** | All comments must be resolved | No ignored feedback. |
|
|
42
|
+
| **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. |
|
|
43
|
+
|
|
44
|
+
**Enforcement (every row above except Merge type): explicitly advisory prose (ARC-023)** —
|
|
45
|
+
prescriptive for whatever platform a consumer repository uses; no Arcane check verifies these
|
|
46
|
+
policies are actually configured, on any platform.
|
|
47
|
+
|
|
48
|
+
For docs-only repos: a PR is not strictly required (a project may allow local ff-only merges for
|
|
49
|
+
pure documentation, per its own governance); a CI lint check can still run on push to any branch;
|
|
50
|
+
branch discipline (branch → work → ff-only merge) still applies regardless.
|
|
51
|
+
|
|
52
|
+
### CI-Skip Semantics: Never Trust Commit Metadata (ARC-022)
|
|
53
|
+
|
|
54
|
+
CI skip/run decisions are based **only on changed paths** — never on commit message, author
|
|
55
|
+
identity, or branch name. Do not add a *new* mechanism (a custom `docs(...)`-style commit-prefix
|
|
56
|
+
convention, a bot rule, a pipeline-level check) that lets a commit opt itself out of validation by
|
|
57
|
+
what it *says* rather than what it *changed*. **Enforcement: explicitly advisory prose (ARC-023) —
|
|
58
|
+
prescriptive guidance for any consumer CI platform; this repo's own `.github/workflows/ci.yml` runs
|
|
59
|
+
unconditionally on every push/PR to `main` with no path filter at all, so it neither implements nor
|
|
60
|
+
is bound by this pattern, and no Arcane check inspects a downstream repo's CI configuration for
|
|
61
|
+
compliance.**
|
|
62
|
+
|
|
63
|
+
Commit messages, authorship, and branch names are attacker-controlled metadata — anyone who can
|
|
64
|
+
push a commit controls all three. A CI-skip mechanism keyed on any of them is a bypass primitive:
|
|
65
|
+
it lets a malicious or careless commit self-certify as "safe to skip" regardless of what it
|
|
66
|
+
actually touched. Path-based filtering is the only trust signal this document recognizes, because
|
|
67
|
+
the pipeline itself independently observes the changed paths rather than trusting a claim embedded
|
|
68
|
+
in the commit.
|
|
69
|
+
|
|
70
|
+
**Know your platform's default skip-comment behavior — don't assume this rule alone controls it.
|
|
71
|
+
Enforcement: explicitly advisory prose (ARC-023) — this paragraph discloses a residual
|
|
72
|
+
platform-level gap; it is not a mechanism Arcane enforces.** Most major CI platforms honor some
|
|
73
|
+
form of skip-comment convention in a pushed commit message by default (see the Azure DevOps
|
|
74
|
+
Profile below for the exact syntax that platform recognizes) — this is platform behavior, not
|
|
75
|
+
something a consumer opts into, and this governance rule cannot disable it from pipeline
|
|
76
|
+
configuration alone. The required-before-merge gate a repo's own branch policy relies on should be
|
|
77
|
+
verified to run *regardless* of any such skip-comment convention, so the merge gate itself stays
|
|
78
|
+
sound. What is generally **not** protected: any other trigger-based run a repo depends on for
|
|
79
|
+
signal outside that one merge gate — direct pushes to a non-PR branch, a secondary environment
|
|
80
|
+
build, a dashboard keyed off pipeline status. Treat those as a residual, platform-level exposure
|
|
81
|
+
this document can flag but not close; if a trigger-based run must be tamper-resistant, that
|
|
82
|
+
requires an organization-level policy outside any one repo's own pipeline configuration, not a
|
|
83
|
+
governance sentence.
|
|
84
|
+
|
|
85
|
+
### Branch-Policy Path-Filter Alignment (ARC-022)
|
|
86
|
+
|
|
87
|
+
A pipeline's own trigger path filter and a branch-protection rule's **required-check path filter**
|
|
88
|
+
(where the platform supports scoping a required check to specific paths) are two independent
|
|
89
|
+
mechanisms, and only one of them controls whether a passing build is actually *required* before a
|
|
90
|
+
PR can merge. If the branch-protection path filter is narrower than the pipeline's own trigger
|
|
91
|
+
scope, a change that falls outside the protection filter's scope can merge with
|
|
92
|
+
**no build check required at all** — even though the pipeline itself is correctly scoped and would
|
|
93
|
+
have run and caught a problem, because nothing forced it to be a merge requirement for that
|
|
94
|
+
specific change.
|
|
95
|
+
|
|
96
|
+
Whenever a pipeline's own trigger scope changes, any path-scoped branch-protection requirement (if
|
|
97
|
+
the platform has one) must be reviewed and updated to match or be a strict superset — never left narrower.
|
|
98
|
+
**Enforcement: explicitly advisory prose (ARC-023) — prescriptive for any consumer
|
|
99
|
+
repository with path-scoped branch protection; Arcane has no visibility into a downstream repo's
|
|
100
|
+
branch-protection path-filter configuration and cannot verify the two stay aligned.** See the Azure
|
|
101
|
+
DevOps Profile below for a concrete failure mode observed on that platform.
|
|
102
|
+
|
|
103
|
+
### Deployment Gate Principles
|
|
104
|
+
|
|
105
|
+
| Environment | Gate | Who Approves |
|
|
106
|
+
|-------------|------|-------------|
|
|
107
|
+
| **Staging** | CI passes + PR approved | Automatic on merge. |
|
|
108
|
+
| **Production** | Staging verified + manual approval | Human (operator). |
|
|
109
|
+
| **Infrastructure apply** (Terraform or equivalent) | Plan reviewed + security scan clean | Human (operator). |
|
|
110
|
+
|
|
111
|
+
**Enforcement: explicitly advisory prose (ARC-023)** — describes an intended gating shape for any
|
|
112
|
+
consumer pipeline; Arcane does not verify a downstream repo's environment or approval configuration
|
|
113
|
+
on any platform.
|
|
20
114
|
|
|
21
115
|
---
|
|
22
116
|
|
|
23
|
-
##
|
|
117
|
+
## Azure DevOps Profile
|
|
118
|
+
|
|
119
|
+
Everything below is specific to Azure DevOps — the only platform this document currently details
|
|
120
|
+
in full, applying the core principles above through that platform's actual mechanisms. Owned by
|
|
121
|
+
Prospero (DevOps Engineer) where a roster assigns that role.
|
|
122
|
+
|
|
123
|
+
### Pipeline Matrix
|
|
24
124
|
|
|
25
125
|
| Repo | Org | Pipeline | Triggers | Steps | Owner |
|
|
26
126
|
|------|-----|----------|----------|-------|-------|
|
|
@@ -31,31 +131,25 @@ Pipeline patterns, branch policies, and deployment gates for all your projects.
|
|
|
31
131
|
| storefront / web | {ado-org} | Build + Deploy | PR, merge to main | build → deploy (platform-dependent) | Prospero |
|
|
32
132
|
| legacy-app / App | {ado-org} | Build + Test | PR, merge to main | dotnet restore → build → test | Prospero |
|
|
33
133
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
## Branch Policies (Azure DevOps)
|
|
134
|
+
### Branch Policies (Azure DevOps mechanics)
|
|
37
135
|
|
|
38
|
-
|
|
136
|
+
Applies the Core Principles' branch-policy table above through Azure DevOps's own Branch Policies
|
|
137
|
+
screen (Project Settings → Repositories → Branch Policies), applied to all code repositories (not
|
|
138
|
+
docs-only repos per ADR-048): "Require PR" is Azure DevOps's own require-a-minimum-number-of-
|
|
139
|
+
reviewers policy; "Work item linking" maps to Azure Boards work items; "Build validation" is Azure
|
|
140
|
+
DevOps's own named Build Validation policy type. **Enforcement: explicitly advisory prose
|
|
141
|
+
(ARC-023) — prescriptive for consumer Azure DevOps repos specifically; no Arcane check verifies any
|
|
142
|
+
of these policies are configured there** (Merge type is the one exception — see the Core table's
|
|
143
|
+
own enforcement note, which already covers Azure DevOps).
|
|
39
144
|
|
|
40
|
-
|
|
41
|
-
|--------|---------|-----------|
|
|
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
|
-
|
|
49
|
-
For docs-only repos (Arcane):
|
|
145
|
+
For docs-only repos (Arcane itself, when hosted on Azure DevOps):
|
|
50
146
|
- PR not required (ADR-048 allows local ff-only merge for docs)
|
|
51
147
|
- CI lint check runs on push to any branch
|
|
52
148
|
- Branch discipline still applies (branch → work → ff-only merge)
|
|
53
149
|
|
|
54
|
-
|
|
150
|
+
### Pipeline Templates
|
|
55
151
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
### .NET Backend Pipeline
|
|
152
|
+
#### .NET Backend Pipeline
|
|
59
153
|
|
|
60
154
|
Fail-safe path filter (ARC-022): **exclude** known-inert doc paths rather than **include** a
|
|
61
155
|
named code directory — a new code directory added outside `src/**` would silently never trigger
|
|
@@ -106,7 +200,7 @@ steps:
|
|
|
106
200
|
failIfCoverageEmpty: true
|
|
107
201
|
```
|
|
108
202
|
|
|
109
|
-
|
|
203
|
+
#### Node.js Pipeline
|
|
110
204
|
|
|
111
205
|
Previously had no path filter at all — fail-safe (never misses a code change) but wasteful (a
|
|
112
206
|
pure docs commit still burns a full pipeline run). Fail-safe path filter (ARC-022): the same
|
|
@@ -146,7 +240,7 @@ steps:
|
|
|
146
240
|
condition: succeededOrFailed()
|
|
147
241
|
```
|
|
148
242
|
|
|
149
|
-
|
|
243
|
+
#### Terraform Pipeline
|
|
150
244
|
|
|
151
245
|
An **include** filter is correct here, not the ARC-022 anti-pattern — but only if it's a
|
|
152
246
|
**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
|
|
@@ -202,7 +296,7 @@ steps:
|
|
|
202
296
|
# Apply step is manual — requires approval gate
|
|
203
297
|
```
|
|
204
298
|
|
|
205
|
-
|
|
299
|
+
#### Markdown Lint Pipeline
|
|
206
300
|
|
|
207
301
|
Also correctly include-scoped, for the same reason as the Terraform pipeline: this pipeline
|
|
208
302
|
exists specifically to lint markdown, not to validate code generally, and `**.md` already has the
|
|
@@ -237,67 +331,26 @@ steps:
|
|
|
237
331
|
condition: succeededOrFailed()
|
|
238
332
|
```
|
|
239
333
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
## Never Trust Commit Metadata for CI Skipping (ARC-022)
|
|
243
|
-
|
|
244
|
-
CI skip/run decisions are based **only on changed paths** — never on commit message, author
|
|
245
|
-
identity, or branch name. Do not add a *new* mechanism (a custom `docs(...)`-style commit-prefix
|
|
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*. **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
|
-
|
|
249
|
-
Commit messages, authorship, and branch names are attacker-controlled metadata — anyone who can
|
|
250
|
-
push a commit controls all three. A CI-skip mechanism keyed on any of them is a bypass primitive:
|
|
251
|
-
it lets a malicious or careless commit self-certify as "safe to skip" regardless of what it
|
|
252
|
-
actually touched. Path-based filtering (above) is the only trust signal this repository
|
|
253
|
-
recognizes, because the pipeline itself independently observes the changed paths rather than
|
|
254
|
-
trusting a claim embedded in the commit.
|
|
255
|
-
|
|
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
|
-
`[skip ci]`, `[ci skip]`, `skip-checks: true`, `[skip azurepipelines]`, `[skip azpipelines]`,
|
|
258
|
-
`[skip azp]`, and `***NO_CI***` in a pushed commit message **by default**, on `trigger:`-driven
|
|
259
|
-
runs — this is platform behavior, not something a consumer opts into, and this governance rule
|
|
260
|
-
cannot disable it from the YAML alone. The required-before-merge gate this repo's own Branch
|
|
261
|
-
Policies table relies on (**build validation on the PR's merge commit**) is documented to run
|
|
262
|
-
*regardless* of `[skip ci]` and its variants, so the merge gate itself stays sound. What is **not**
|
|
263
|
-
protected: any other trigger-based run this repo depends on for signal outside that one gate —
|
|
264
|
-
direct pushes to a non-PR branch, a secondary environment build, a dashboard keyed off pipeline
|
|
265
|
-
status. Treat those as a residual, platform-level exposure this document can flag but not close;
|
|
266
|
-
if a trigger-based run must be tamper-resistant, that requires an organization-level policy
|
|
267
|
-
outside this repo's own YAML, not a governance sentence.
|
|
268
|
-
|
|
269
|
-
## Branch-Policy Path-Filter Alignment (ARC-022)
|
|
270
|
-
|
|
271
|
-
A YAML pipeline's own `trigger`/`pr` path filter and an Azure DevOps branch policy's **build
|
|
272
|
-
validation path filter** are two independent mechanisms, and only one of them controls whether a
|
|
273
|
-
passing build is actually *required* before a PR can merge. If the branch policy's path filter is
|
|
274
|
-
narrower than the pipeline's own trigger scope, a change that falls outside the policy's filter
|
|
275
|
-
can merge with **no build check required at all** — even though the pipeline itself is correctly
|
|
276
|
-
scoped and would have run and caught a problem, because nothing forced it to be a merge
|
|
277
|
-
requirement for that specific change.
|
|
278
|
-
|
|
279
|
-
**Concrete failure mode:** the .NET pipeline above triggers on everything except docs (exclude
|
|
280
|
-
filter, fail-safe). If the repo's branch policy still requires the build check only for changes
|
|
281
|
-
under `paths: src/**` (an older include-style policy filter, or one configured before this repo
|
|
282
|
-
adopted exclude-based YAML triggers), a change to a new top-level code directory outside `src/`
|
|
283
|
-
triggers the pipeline (YAML is fail-safe) but does **not** require it to pass before merge (branch
|
|
284
|
-
policy is not) — the build can run, fail, and be ignored, or the PR can merge before it finishes.
|
|
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. **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.**
|
|
334
|
+
### Azure Pipelines' Skip-CI Syntax (the Core section's "know your platform's default")
|
|
287
335
|
|
|
288
|
-
|
|
336
|
+
Azure Pipelines honors `[skip ci]`, `[ci skip]`, `skip-checks: true`, `[skip azurepipelines]`,
|
|
337
|
+
`[skip azpipelines]`, `[skip azp]`, and `***NO_CI***` in a pushed commit message **by default**, on
|
|
338
|
+
`trigger:`-driven runs. The required-before-merge gate this document's own Branch Policies section
|
|
339
|
+
relies on (**build validation on the PR's merge commit**) is documented to run *regardless* of
|
|
340
|
+
`[skip ci]` and its variants, so the merge gate itself stays sound. What is **not** protected: any
|
|
341
|
+
other trigger-based run a repo depends on for signal outside that one gate — direct pushes to a
|
|
342
|
+
non-PR branch, a secondary environment build, a dashboard keyed off pipeline status.
|
|
289
343
|
|
|
290
|
-
|
|
344
|
+
### Branch-Policy Path-Filter Alignment: a concrete Azure DevOps failure mode
|
|
291
345
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
---
|
|
346
|
+
The .NET pipeline above triggers on everything except docs (exclude filter, fail-safe). If the
|
|
347
|
+
repo's branch policy still requires the build check only for changes under `paths: src/**` (an
|
|
348
|
+
older include-style policy filter, or one configured before this repo adopted exclude-based YAML
|
|
349
|
+
triggers), a change to a new top-level code directory outside `src/` triggers the pipeline (YAML is
|
|
350
|
+
fail-safe) but does **not** require it to pass before merge (branch policy is not) — the build can
|
|
351
|
+
run, fail, and be ignored, or the PR can merge before it finishes.
|
|
299
352
|
|
|
300
|
-
|
|
353
|
+
### Prospero's Responsibilities
|
|
301
354
|
|
|
302
355
|
1. **Create and maintain all CI/CD pipelines** across Azure DevOps organizations
|
|
303
356
|
2. **Configure branch policies** per repo risk level
|
|
@@ -308,9 +361,7 @@ one) must be reviewed and updated to match or be a strict superset — never lef
|
|
|
308
361
|
7. **Deployment automation** — Azure Functions, App Service, container deployments
|
|
309
362
|
8. **Documentation** — keep this file and pipeline configs in sync
|
|
310
363
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
## Implementation Priority
|
|
364
|
+
### Implementation Priority
|
|
314
365
|
|
|
315
366
|
1. WidgetApp .NET backend pipeline (highest business priority)
|
|
316
367
|
2. ops docs lint pipeline (catches documentation drift)
|
|
@@ -319,6 +370,7 @@ one) must be reviewed and updated to match or be a strict superset — never lef
|
|
|
319
370
|
5. Terraform pipeline (when new Azure resources are provisioned)
|
|
320
371
|
6. AcmeStore pipeline (when platform is selected)
|
|
321
372
|
|
|
373
|
+
---
|
|
322
374
|
|
|
323
375
|
## Related
|
|
324
376
|
|
|
@@ -189,14 +189,14 @@ Without this chain, agents make conflicting decisions across stories (e.g., one
|
|
|
189
189
|
|
|
190
190
|
### Adversarial Review
|
|
191
191
|
|
|
192
|
-
`spell-review` requires the reviewer to **
|
|
192
|
+
`spell-review` requires the reviewer to **cover every lens, not hit a finding count**. "Looks good" without addressing each lens is not an acceptable review. The reviewer must:
|
|
193
193
|
|
|
194
|
-
1.
|
|
195
|
-
2. Classify each as HIGH / MEDIUM / LOW severity
|
|
194
|
+
1. Explicitly cover every lens — correctness, security, performance, tests, naming/clarity, architecture — and state "no issues" for any lens that is clean. There is no finding quota: zero findings is a valid outcome on a clean or small diff; never fabricate issues to hit one.
|
|
195
|
+
2. Classify each real finding as HIGH / MEDIUM / LOW severity
|
|
196
196
|
3. Check for missing test coverage, architecture violations, security issues
|
|
197
197
|
4. Defer unrelated findings to a backlog (don't derail the current change)
|
|
198
198
|
|
|
199
|
-
**Enforcement: explicitly advisory prose (ARC-023) — no script counts or verifies
|
|
199
|
+
**Enforcement: explicitly advisory prose (ARC-023) — no script counts or verifies lens coverage; this text now matches `spell-review.prompt.md`'s own current Rules section (coverage of effort, not a count of findings) rather than the superseded "minimum of 3" framing an earlier version of this rule stated.**
|
|
200
200
|
|
|
201
201
|
### Append-Only Progress
|
|
202
202
|
|
|
@@ -339,7 +339,7 @@ A tool that writes code **must not also run `spell-review` on that same code. En
|
|
|
339
339
|
### Minimum gate for any Copilot-produced code
|
|
340
340
|
|
|
341
341
|
```
|
|
342
|
-
Copilot implements → spell-test (run tests, check coverage) → spell-review (adversarial pass,
|
|
342
|
+
Copilot implements → spell-test (run tests, check coverage) → spell-review (adversarial pass, every lens covered) → spell-ship
|
|
343
343
|
```
|
|
344
344
|
|
|
345
345
|
This applies regardless of change size. Even single-file bug fixes must pass `spell-review` before being pushed to `origin/main`.
|
|
@@ -35,7 +35,7 @@ Paste this entire file into any AI client (Claude web, ChatGPT, etc.) to give it
|
|
|
35
35
|
5. **Log significant decisions** in DECISIONS.md as ADRs.
|
|
36
36
|
6. **Production system** — no shortcuts, no "fix it later."
|
|
37
37
|
7. **Conventional Commits** format: `type(scope): description`.
|
|
38
|
-
8. **Never auto-commit** during interactive sessions — present for approval first.
|
|
38
|
+
8. **Never auto-commit** during interactive sessions — present for approval first. Exception: autonomous agents at Magus+ power level may self-commit within approved scope.
|
|
39
39
|
9. **Recommendation guardrails:** Flag actionable recommendations. Verify information. Present free alternatives. Confirm >$50 items.
|
|
40
40
|
|
|
41
41
|
## Key Files
|
|
@@ -50,7 +50,7 @@ this broader ≥ Silver gate is not fully verified by any coded scorer or workfl
|
|
|
50
50
|
| --- | --- |
|
|
51
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
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. |
|
|
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. |
|
|
53
|
+
| Gold | + no hard assumption of a specific tracker, CI/CD platform, cloud/deployment vendor, agent roster, or directory layout — the spell works in a vanilla consuming repo with no Arcane context files present. |
|
|
54
54
|
|
|
55
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.
|
|
56
56
|
|
|
@@ -136,6 +136,18 @@ Arcane spells ship to other repositories and, eventually, open source. Keep them
|
|
|
136
136
|
[[development-methodology]] — point to them rather than re-inlining ADO logic. Enforcement: explicitly
|
|
137
137
|
advisory prose (ARC-023) — self-graded during spell audit; no lint scans spell prompts for a
|
|
138
138
|
hard-coded tracker assumption.
|
|
139
|
+
- **CI/CD and deployment vendors are optional too (ARC-038 decision 3):** the same D2 bar that
|
|
140
|
+
forbids assuming a single tracker applies to any other platform a spell might otherwise hard-code
|
|
141
|
+
— a CI/CD system, a cloud provider, a deployment target. When a governance doc's actual content
|
|
142
|
+
turns out to be vendor-specific throughout (found, not assumed: [[cicd-standards]] was
|
|
143
|
+
Azure-DevOps-specific end to end until ARC-038), the remediation is the same shape decision 2
|
|
144
|
+
demonstrates concretely there — split into a vendor-neutral core (principles) plus a
|
|
145
|
+
provider-specific profile (that vendor's mechanics), the same shape
|
|
146
|
+
[[development-methodology]] already uses for tracking providers. No separate "vendor-specific
|
|
147
|
+
standards directory" — the pattern is the split itself, applied to whichever governance doc or
|
|
148
|
+
spell risks the same coupling next. Enforcement: explicitly advisory prose (ARC-023) — self-graded
|
|
149
|
+
during spell audit; no lint scans a spell prompt or governance doc for a hard-coded CI/CD or
|
|
150
|
+
deployment vendor assumption.
|
|
139
151
|
|
|
140
152
|
> **Maintainer-internal exemption.** A few spells operate *on the Arcane framework itself*
|
|
141
153
|
> (e.g. `spell-bump`, `spell-arcane-version`) and legitimately reference repo internals like
|
|
@@ -53,13 +53,13 @@ How a web property becomes retrievable by search-engine crawlers and by the AI a
|
|
|
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. 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
|
|
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 no longer cites WD-02 — its Open Graph route-specificity check is unlabeled supplementary evidence, not a claim about this rule.**
|
|
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. Enforcement:
|
|
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: structured spell gate (ARC-023) — spell-make-discoverable's Phase 2 Audit table (Per-route share metadata, WD-03) fetches a known-dead or non-public route and requires a genuine `404`/`410` status, not an always-`200` shell, before Phase 3's approval gate allows any fix to proceed.**
|
|
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. 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
|
|
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 no longer cites WD-04 — its JSON-LD validity check is unlabeled supplementary evidence, not a claim about this rule.**
|
|
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. 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
|
|
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 no longer cites WD-05 — its basic robots.txt validity check is unlabeled supplementary evidence, not a claim about this rule.**
|
|
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,7 +97,7 @@ 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. 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
|
|
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 no longer cites WD-06 — its sitemap-directive-presence check is unlabeled supplementary evidence, not a claim about this rule.**
|
|
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
|
|
|
@@ -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. Enforcement:
|
|
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: structured spell gate (ARC-023) — spell-make-discoverable's Phase 2 Audit table (Robots and sitemap policy, WD-08) checks that a specific `Allow:` rule is declared for any public path a broader `Disallow:` prefix would otherwise swallow, before Phase 3's approval gate allows any fix to proceed.**
|
|
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
|
|
|
@@ -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. 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
|
|
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 no longer cites WD-14 — its restricted-path sitemap-exclusion check is unlabeled supplementary evidence, and share-image fetchability/size is captured without a WD-nn citation as well.**
|
|
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. 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
|
|
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 no longer cites WD-15 — its share-image fetchability check is unlabeled supplementary evidence, not a claim about this rule.**
|
|
204
204
|
|
|
205
205
|
---
|
|
206
206
|
|
|
@@ -107,33 +107,38 @@ for each of `{NONPROD_ORIGINS}`:
|
|
|
107
107
|
|
|
108
108
|
For each check below, capture the stated evidence and mark it
|
|
109
109
|
`PASS`/`FAIL`. **Do not restate any rule's justification here — cite the ID
|
|
110
|
-
only**; the reasoning lives in `web-discoverability-standards.md`.
|
|
110
|
+
only**; the reasoning lives in `web-discoverability-standards.md`. A row
|
|
111
|
+
marked *(supplementary)* is a useful check that doesn't correspond to any
|
|
112
|
+
single `WD-nn` rule — capture its evidence the same way, but don't cite a
|
|
113
|
+
rule ID for it.
|
|
111
114
|
|
|
112
115
|
**Per-route share metadata**
|
|
113
116
|
|
|
114
117
|
| Rule ID | Check | Evidence to capture |
|
|
115
118
|
| --- | --- | --- |
|
|
116
119
|
| `WD-01` | Fetch `{SITE_ORIGIN}`/<a specific deep route, not the homepage> with a plain HTTP client that does not execute JavaScript. Does the raw response body carry a `<title>` and meta description distinct from the homepage's? | The two raw response bodies, with the extracted `<title>`/description values highlighted. |
|
|
117
|
-
|
|
|
118
|
-
| `WD-03` |
|
|
119
|
-
|
|
|
120
|
+
| *(supplementary)* | In that same response, do the Open Graph / social-card tags (`og:title`, `og:description`, `og:image`) reflect the route's own content, not the homepage's or a hard-coded default? | Extracted `og:*` tag values per sampled route. |
|
|
121
|
+
| `WD-03` | Fetch a route representing content that no longer exists, or that was never public. Does the response return a genuine `404` or `410` status — not a `200` that merely renders a not-found-looking page? | The response status code, and the rendered body (to confirm it's the application's own not-found UI, not a generic host error page). |
|
|
122
|
+
| *(supplementary)* | Does each fetched route carry exactly one `<link rel="canonical">`, self-referential or pointing at the one intended canonical URL, with no second conflicting canonical tag? | Canonical URL(s) found per route; flag any route with more than one. |
|
|
123
|
+
| *(supplementary)* | Where the route type is expected to carry structured data, is an `application/ld+json` block present and does it parse as valid JSON? | The raw JSON-LD block per route, and the parse result. |
|
|
120
124
|
| `WD-13` | Where a share-metadata or structured-data value is built from user- or content-supplied text (e.g. a title containing a quote or ampersand), is it correctly escaped for both contexts it appears in — JSON-escaped inside the JSON-LD payload, and separately HTML-escaped in surrounding markup — with no raw pass-through? | One sampled route whose content contains a quote, ampersand, or angle bracket, with the rendered JSON-LD and HTML attribute shown side by side. |
|
|
121
|
-
|
|
|
125
|
+
| *(supplementary)* | Fetch a sampled `{PUBLIC_ASSET_PREFIX}` share-image URL with a plain, unauthenticated HTTP client. Does it return `200` with an image content-type and no auth redirect? | Response status, `Content-Type` header, and byte size for the sampled asset. |
|
|
122
126
|
|
|
123
127
|
**Robots and sitemap policy**
|
|
124
128
|
|
|
125
129
|
| Rule ID | Check | Evidence to capture |
|
|
126
130
|
| --- | --- | --- |
|
|
127
|
-
|
|
|
128
|
-
|
|
|
131
|
+
| *(supplementary)* | Fetch `{SITE_ORIGIN}/robots.txt` directly. Does it exist, return `200`, parse as valid, and avoid disallowing any route intended to be indexed? | Raw `robots.txt` body, and any `Disallow` line matching an intended-public route. |
|
|
132
|
+
| *(supplementary)* | Does `robots.txt` contain a `Sitemap:` directive, and does fetching that URL return a well-formed sitemap? | The `Sitemap:` line and the fetched sitemap's parse result. |
|
|
129
133
|
| `WD-07` | Does every URL listed in the sitemap satisfy `{PUBLIC_VISIBILITY_PREDICATE}`? | Any sitemap entries that fail the predicate. |
|
|
130
|
-
| `WD-
|
|
134
|
+
| `WD-08` | Where `{PUBLIC_ASSET_PREFIX}` (or another specific public path referenced by page metadata) falls under a broader disallowed prefix in `robots.txt`, is an explicit `Allow:` rule declared for that specific path? | The relevant `Allow:`/`Disallow:` lines from `robots.txt`, and confirmation the specific path is fetchable by a crawler despite the broader disallow. |
|
|
135
|
+
| *(supplementary)* | For a sampled route under `{RESTRICTED_PREFIX}`: is it absent from the sitemap, does it carry a noindex signal, and is it unreachable via any link from a route that satisfies `{PUBLIC_VISIBILITY_PREDICATE}`? | Sitemap membership (should be none), noindex signal, and an inbound-link check from the public route set. |
|
|
131
136
|
|
|
132
137
|
**Non-production noindexing**
|
|
133
138
|
|
|
134
139
|
| Rule ID | Check | Evidence to capture |
|
|
135
140
|
| --- | --- | --- |
|
|
136
|
-
|
|
|
141
|
+
| *(supplementary)* | For each `{NONPROD_ORIGINS}` host: is a site-wide `Disallow: /` in `robots.txt` the *only* suppression signal present — no accompanying noindex header or meta tag? Closely related to `WD-09` below. | The host's `robots.txt` plus presence/absence of a noindex header and meta tag on a sampled route. |
|
|
137
142
|
| `WD-09` | For each `{NONPROD_ORIGINS}` host: does a sampled route serve **both** an `X-Robots-Tag: noindex` response header **and** an HTML `<meta name="robots" content="noindex">` tag, gated by `{ENV_SIGNAL}`? | Response headers and parsed `<head>` for one sampled route per non-prod host. |
|
|
138
143
|
|
|
139
144
|
**Search-engine registration**
|