diff --git a/AGENTS.md b/AGENTS.md index c18aeeb3..e7dfd036 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -151,6 +151,9 @@ labels are terminal, applied when closing without action: `duplicate` - A check that derives a comparison baseline by running a command MUST verify the command produced one and refuse when it did not — the refusal is a finding, never exit 3; see `base-quality-gates` +- Never name a template file's path in a template's prose: an emitted + project carries the rules, not the templates. Name the section by its + heading instead; SYS-27 checks every root's emitted files - Never name another file's section ID in running prose unless that file resolves into every chain carrying yours — state the substance inline instead; SYS-11 checks it per chain (spec, chapter 8.4) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6527c756..5da05d33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -258,6 +258,8 @@ Releases up to 2.63.0 are listed in ### Fixed +- Emitted skills and root files no longer point to template files a + project does not carry ([#2415]) - Cached personal data follows its source's access and deletion rules instead of being banned ([#2370]) - The audit trail records that a PII field changed, not its values, so @@ -486,6 +488,7 @@ Releases up to 2.63.0 are listed in [#2386]: https://github.com/solid-ai-dev/solid-ai-templates/pull/2386 [#2387]: https://github.com/solid-ai-dev/solid-ai-templates/pull/2387 [#2389]: https://github.com/solid-ai-dev/solid-ai-templates/pull/2389 +[#2415]: https://github.com/solid-ai-dev/solid-ai-templates/pull/2415 ## [2.90.0] - 2026-09-12 diff --git a/docs/architecture/08-crosscutting-concepts.md b/docs/architecture/08-crosscutting-concepts.md index bd6f8479..88bf394a 100644 --- a/docs/architecture/08-crosscutting-concepts.md +++ b/docs/architecture/08-crosscutting-concepts.md @@ -212,43 +212,43 @@ task starts. The root file's count includes its fact slots. | Root | Every turn, words | Skills | Skill words | |------|-------------------|--------|-------------| -| `backend-api` | 5730 | 11 | 2721 | -| `backend-auth` | 7782 | 10 | 2101 | -| `backend-caching` | 5389 | 10 | 2101 | -| `backend-concurrency` | 5236 | 10 | 2101 | -| `backend-database` | 5196 | 11 | 2180 | -| `backend-edge` | 8118 | 10 | 2101 | -| `backend-errors` | 6635 | 10 | 2101 | -| `backend-features` | 5247 | 10 | 2101 | -| `backend-grpc` | 8894 | 11 | 2257 | -| `backend-http` | 5663 | 10 | 2101 | -| `backend-jobs` | 5169 | 10 | 2101 | -| `backend-messaging` | 5455 | 11 | 2195 | -| `backend-microservices` | 6852 | 11 | 2860 | -| `backend-monitoring` | 5528 | 10 | 2101 | -| `backend-observability` | 5275 | 10 | 2101 | -| `backend-webhooks` | 8637 | 11 | 2195 | -| `base-agents` | 4797 | 10 | 3194 | -| `base-cicd` | 5085 | 10 | 2221 | -| `base-config` | 5259 | 10 | 2131 | -| `base-containers` | 5138 | 10 | 2201 | -| `base-data-governance` | 5694 | 10 | 2101 | -| `base-data-migration` | 5367 | 11 | 2473 | -| `base-data-modeling` | 5206 | 10 | 2101 | -| `base-data-quality` | 5961 | 11 | 2150 | -| `base-deployment` | 5075 | 10 | 2160 | -| `base-devsecops` | 5383 | 10 | 2101 | -| `base-docs` | 4781 | 10 | 2101 | -| `base-embedded-checks` | 6035 | 11 | 4382 | -| `base-git` | 4781 | 10 | 2101 | -| `base-measurement` | 5508 | 10 | 2101 | -| `base-oop` | 5323 | 10 | 2101 | -| `base-quality` | 4781 | 10 | 2101 | -| `base-quality-gates` | 6014 | 11 | 4139 | -| `base-review` | 4781 | 10 | 2101 | -| `base-security` | 6312 | 10 | 2101 | -| `base-testing` | 4781 | 10 | 2101 | -| `frontend-content` | 5176 | 10 | 2101 | -| `frontend-quality` | 5201 | 10 | 2176 | -| `frontend-ux` | 5283 | 10 | 2101 | +| `backend-api` | 5730 | 11 | 2714 | +| `backend-auth` | 7784 | 10 | 2094 | +| `backend-caching` | 5389 | 10 | 2094 | +| `backend-concurrency` | 5236 | 10 | 2094 | +| `backend-database` | 5196 | 11 | 2173 | +| `backend-edge` | 8117 | 10 | 2094 | +| `backend-errors` | 6635 | 10 | 2094 | +| `backend-features` | 5247 | 10 | 2094 | +| `backend-grpc` | 8896 | 11 | 2250 | +| `backend-http` | 5663 | 10 | 2094 | +| `backend-jobs` | 5169 | 10 | 2094 | +| `backend-messaging` | 5455 | 11 | 2188 | +| `backend-microservices` | 6852 | 11 | 2853 | +| `backend-monitoring` | 5528 | 10 | 2094 | +| `backend-observability` | 5275 | 10 | 2094 | +| `backend-webhooks` | 8637 | 11 | 2188 | +| `base-agents` | 4797 | 10 | 3189 | +| `base-cicd` | 5085 | 10 | 2214 | +| `base-config` | 5259 | 10 | 2124 | +| `base-containers` | 5138 | 10 | 2194 | +| `base-data-governance` | 5694 | 10 | 2094 | +| `base-data-migration` | 5367 | 11 | 2466 | +| `base-data-modeling` | 5206 | 10 | 2094 | +| `base-data-quality` | 5961 | 11 | 2143 | +| `base-deployment` | 5075 | 10 | 2153 | +| `base-devsecops` | 5383 | 10 | 2094 | +| `base-docs` | 4781 | 10 | 2094 | +| `base-embedded-checks` | 6035 | 11 | 4377 | +| `base-git` | 4781 | 10 | 2094 | +| `base-measurement` | 5508 | 10 | 2094 | +| `base-oop` | 5323 | 10 | 2094 | +| `base-quality` | 4781 | 10 | 2094 | +| `base-quality-gates` | 6014 | 11 | 4134 | +| `base-review` | 4781 | 10 | 2094 | +| `base-security` | 6312 | 10 | 2094 | +| `base-testing` | 4781 | 10 | 2094 | +| `frontend-content` | 5176 | 10 | 2094 | +| `frontend-quality` | 5201 | 10 | 2169 | +| `frontend-ux` | 5283 | 10 | 2094 | diff --git a/docs/requirements.md b/docs/requirements.md index 4f72d15f..aac0bc79 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -69,6 +69,7 @@ Status: skeleton. Each cluster holds a few requirements until its fill | FR-06-04 | The system shall list each skill in the root file with the task that triggers it. | ADR-058 | SYS-26 | | FR-06-05 | The system shall reference a section marked `check` by its command and pass condition, never by copying the script body. | ADR-058 |SYS-26, partial, on a planted fixture | | FR-06-06 | The system shall build byte-identical files from the same input. | ADR-058 | SYS-26 | +| FR-06-07 | The system shall build files that name no template file, since an emitted project carries no templates. | ADR-058 | SYS-27 | ### 07. Shipped checks diff --git a/docs/tests/index.md b/docs/tests/index.md index 5ee3cad4..8658b251 100644 --- a/docs/tests/index.md +++ b/docs/tests/index.md @@ -50,6 +50,7 @@ restated in each. | `SAIT-SMK-SYS-24-001A` | SMK | P1 | A new section ID never splits a frozen section | | `SAIT-SMK-SYS-25-001A` | SMK | P1 | A load directive names a listed task, in its heading's tags | | `SAIT-SMK-SYS-26-001A` | SMK | P1 | The emitter places each section by its load, the same every run | +| `SAIT-SMK-SYS-27-001A` | SMK | P1 | An emitted file names no template file | | `SAIT-SMK-TPL-04-001A` | SMK | P1 | All EXTEND and OVERRIDE directives reference existing IDs | | `SAIT-INT-TPL-01-001A` | INT | P0 | DEPENDS ON chain assembles a complete rule set | | `SAIT-INT-TPL-02-001A` | INT | P1 | EXTEND adds rules without removing base rules | diff --git a/docs/tests/specs/SAIT-SMK-SYS-27-001A.md b/docs/tests/specs/SAIT-SMK-SYS-27-001A.md new file mode 100644 index 00000000..60d393c9 --- /dev/null +++ b/docs/tests/specs/SAIT-SMK-SYS-27-001A.md @@ -0,0 +1,85 @@ +--- +id: SAIT-SMK-SYS-27-001A +title: An emitted file names no template file +product: sait +type: smoke +area: SYS +priority: p1 +status: ready +environment: [local, ci] +automatable: yes +created: 2026-10-30 +author: Branimir Georgiev +product-version: "3.x" +tags: [tooling, composition, delivery] +--- + +## Short description + +> **Given** every root a project can pick +> **When** the emitter builds each root on its own, with skills and inlined +> **Then** no line of an emitted file names a template file, because an +> emitted project carries the rules and not the templates, so a named +> template is a file the agent cannot open and the rules it stands for +> never reach it + +Each root gets its own build because a project may pick it alone. Built +together, an override one root carries can drop the section another root +names a template in, and the path goes unseen. + +## Results + +| Result | Condition | +|--------|-----------| +| PASSED | The planted template's two named paths are both found in each mode, the planted override hides one only when the roots are built together, and no file emitted from any root alone, in either mode, names a template file | +| FAILED | An emitted line names a template file, with or without its `templates/` prefix, or the planted scan misses a form | +| SKIPPED | — | +| BLOCKED | — | +| ERROR | — | + +## Steps + +### Prerequisites + +- Repository cloned locally +- Python 3 + +### Setup + +— (operates on planted templates and on every root, built in memory; no +file is written) + +### Execution + +1. Build a planted template that names its own path once with the + `templates/` prefix, in a skill, and once without, in a section a + second planted root overrides +2. Build the planted roots together, then each on its own +3. Build every root `--roots` lists on its own, with skills and then with + skills inlined +4. Scan every emitted line for the path of every template file + +### Assertions + +1. Assert the scan finds both of the planted template's named paths, in + each mode +2. Assert the planted roots built together show one of the two paths, and + built each on its own show both +3. Assert no line emitted from any root, in either mode, names a template + file + +### Negative controls + +1. **The prefix-less form unread.** With the scan matching only paths that + start `templates/`, the planted case finds one of its two paths and + fails +2. **A pointer left in a template.** With the templates before this check + landed, the scan fails on the seven lines that named another template, + in every root that carries them +3. **Every root built together.** With the planted roots built as one + chain, the override hides the second path and the planted case fails + +## Related + +- ADR-058: templates place each rule, and task procedures load on demand +- SYS-26: the emitter places each section by its load diff --git a/templates/backend/auth.md b/templates/backend/auth.md index 781e3d66..af2221a3 100644 --- a/templates/backend/auth.md +++ b/templates/backend/auth.md @@ -4,7 +4,7 @@ Rules for identity verification (authn) and access control (authz). Applies to any backend service that has protected resources. -Extends `security/security.md`, "Authentication" (`security-authn`) and +Extends the security rules "Authentication" (`security-authn`) and "Session management" (`security-sessions`), with backend-specific depth. ## General principles diff --git a/templates/backend/edge.md b/templates/backend/edge.md index c7718899..f4f74f77 100644 --- a/templates/backend/edge.md +++ b/templates/backend/edge.md @@ -27,8 +27,7 @@ mesh follows the microservices rules instead. front of it; an over-broad list lets callers spoof their IP and dodge rate limits - Propagate or originate a correlation/request ID at the edge so a - request is traceable end to end (see `backend/observability.md`, - "Distributed tracing") + request is traceable end to end (see "Distributed tracing") ## Routing and upstreams diff --git a/templates/core/agents.md b/templates/core/agents.md index 342aba8c..99c6ae95 100644 --- a/templates/core/agents.md +++ b/templates/core/agents.md @@ -129,7 +129,7 @@ not a note. ### 3.1 Testing ## 4. Review process -[Priority order and checklists from templates/core/review.md] +[Priority order and checklists from the review rules] ## 5. Session protocol [How a session starts, proceeds and ends] diff --git a/templates/core/docs.md b/templates/core/docs.md index 7f21e3fa..c7c17570 100644 --- a/templates/core/docs.md +++ b/templates/core/docs.md @@ -22,8 +22,7 @@ Which documents a project keeps, and how they stay true. [LOAD: write-docs] -- Carry these three in every project (MUST); their layout is the project's - own, and `templates/core/agents.md` offers a default for the agent files +- Carry these three in every project (MUST); their layout is the project's own | File | Purpose | | --- | --- | diff --git a/templates/core/quality-gates.md b/templates/core/quality-gates.md index dd51d420..f39c38db 100644 --- a/templates/core/quality-gates.md +++ b/templates/core/quality-gates.md @@ -49,8 +49,8 @@ the limit, and the check then exits non-zero. Committed generated artifacts — rendered docs, configs, resolved chains, schema output — drift silently without a gate. -- Wire the `--check` invocation from `core/docs.md`, "Generated files", - into CI as a required status check on the relevant paths +- Wire the `--check` invocation from "Generated files" into CI as a + required status check on the relevant paths - Generate hand-written content that reproduces tool output, such as a worked example, behind a marker, so the gate covers it - Enumerate the derivable blocks inside each artifact, not only the @@ -98,7 +98,7 @@ informally. - Run a check whose subject is the change under review in the environment that builds its tree - Report what a check inspected, not only what it found; the test-suite - form of this rule is `core/testing.md`, "Empty results" + form of this rule is "Empty results", in the testing rules - Report how many checks a tool could not read beside how many it ran (MUST) - Report every finding before exiting; where a check cannot continue past diff --git a/templates/core/review.md b/templates/core/review.md index 22032164..4744b39a 100644 --- a/templates/core/review.md +++ b/templates/core/review.md @@ -16,9 +16,9 @@ How a change is reviewed, and how a finding is proven before it is reported. [LOAD: review] -Review in this order, most critical first. `templates/core/quality.md`, and -any language quality template the project carries, is the standard for -items 2 to 4. +Review in this order, most critical first. The quality rules, and any +language quality rules the project carries, are the standard for items 2 +to 4. 1. **Security**: anything exploitable, any credential or licence problem 2. **Correctness**: wrong results, unhandled failures, race conditions diff --git a/tests/smoke/registry.py b/tests/smoke/registry.py index 27a42492..e8aaea86 100644 --- a/tests/smoke/registry.py +++ b/tests/smoke/registry.py @@ -11,7 +11,7 @@ check_sys_07, check_sys_09, check_sys_10, check_sys_11, check_sys_13, check_sys_14, check_sys_15, check_sys_16, check_sys_17, check_sys_18, check_sys_19, check_sys_20, check_sys_21, check_sys_22, check_sys_24, - check_sys_25, check_sys_26) + check_sys_25, check_sys_26, check_sys_27) from .templates import ( check_tpl_01, check_tpl_02, check_tpl_03, check_tpl_04, check_tpl_06, check_tpl_07, check_tpl_08, check_tpl_09, check_tpl_10) @@ -120,4 +120,7 @@ {"id": "SYS-26", "spec": "SAIT-SMK-SYS-26-001A", "title": "The emitter places each section by its load, the same every run", "fn": check_sys_26}, + {"id": "SYS-27", "spec": "SAIT-SMK-SYS-27-001A", + "title": "An emitted file names no template file", + "fn": check_sys_27}, ] diff --git a/tests/smoke/system.py b/tests/smoke/system.py index 0cbeaf9a..2778671d 100644 --- a/tests/smoke/system.py +++ b/tests/smoke/system.py @@ -16,7 +16,8 @@ from emit_context import build as emit_build from emit_context import write as emit_write from resolve_chain import ( - LOAD_CHECK, entry_sections, is_fixture, load_directives, parse_manifest, + LOAD_CHECK, entry_sections, is_fixture, load_catalogue, load_directives, + load_manifest, opt_in_roots, parse_manifest, read_file, resolve_chain, task_kinds) from testkit import ROOT, repository_files, template_files @@ -2186,3 +2187,106 @@ def check_sys_26(): if trees[0][k] != trees[1][k]}) failures.append(f" two runs differ in: {', '.join(differ)}") return seen.failures() + failures, seen.notes() + + +# --------------------------------------------------------------------------- +# SYS-27 -- an emitted file names no template file +# --------------------------------------------------------------------------- + +# An emitted project carries the rules, not the templates they came from, so +# a template's path in an emitted file sends the agent to a file it cannot +# open, and the rules that line calls the standard never reach it. Every +# root is emitted on its own, with skills and inlined, and no line may name a +# template file, with or without its `templates/` prefix. Each root gets its +# own build because a project may pick one root alone: an override another +# root carries could drop the section that names the path, and a build of +# every root together would then hide it. A planted template naming itself +# both ways proves the scan finds each form, and a planted second root that +# overrides one of those sections proves each root is scanned on its own. + +PATH_TEMPLATE = ("# Base — Demo\n[ID: base-demo]\n\n## Review\n[LOAD: release]\n\n" + "- `templates/core/demo.md` is the standard\n\n" + "## Naming\n[ID: demo-naming]\n\n" + "- See `core/demo.md`, \"Naming\"\n") +PATH_STACK = ("# Stack — Demo\n\n## Naming\n[OVERRIDE: demo-naming]\n\n" + "- Name by role\n") +PATH_KINDS = {"release": "Preparing a release"} +PATH_CHAINS = { + "demo": [("templates/core/demo.md", PATH_TEMPLATE)], + "stack": [("templates/core/demo.md", PATH_TEMPLATE), + ("templates/fixture/stack.md", PATH_STACK)]} + + +def path_findings(emitted, paths): + """Each emitted file and line that names one of `paths`, a template's + path under the repository.""" + patterns = [(path, re.compile( + r"(?