From 349392d13b720a9a311b34c6cdc36561ca5ec857 Mon Sep 17 00:00:00 2001 From: Peter Krenesky Date: Thu, 1 Oct 2026 12:47:21 -0700 Subject: [PATCH] chore: delete recorded pins, baselines and version copies Derive the generator's semantic-core base from the manifest's declared semantic.semantic_core. Delete the baseline-0.1.0 fixture, the additive compatibility tests, the baseline-reading manifest tests, NFR-001 with its criteria and test rows, the FR-003 baseline and version-literal criteria, the freeze-mechanism text in spec.md, the @typespec/compiler version in the Node error text, and version, revision and origin/main prose in the spec, manifest comments, tests and IT-002. Emitted schemas are byte-identical. Co-Authored-By: Claude Sonnet 5.5 --- scripts/generate-schemas.mjs | 22 +- .../FR-003-semantic-manifest-contract.md | 16 +- spec/index.md | 1 - .../IT-002-quoin-module-install.md | 7 +- .../NFR-001-additive-compatibility.md | 97 ---- spec/non-functional/index.md | 10 - spec/spec.md | 18 +- spec/tests.md | 47 +- spec_objects_security/manifest.yaml | 88 +-- tests/conftest.py | 1 - tests/fixtures/baseline-0.1.0/manifest.yaml | 509 ------------------ .../baseline-0.1.0/skeletons/asset.md | 22 - .../skeletons/attack_surface.md | 25 - .../baseline-0.1.0/skeletons/audit_event.md | 34 -- .../baseline-0.1.0/skeletons/audit_finding.md | 25 - .../baseline-0.1.0/skeletons/auth_flow.md | 31 -- .../baseline-0.1.0/skeletons/control.md | 24 - .../baseline-0.1.0/skeletons/cors_policy.md | 24 - .../baseline-0.1.0/skeletons/csrf_token.md | 19 - .../skeletons/data_classification.md | 25 - .../skeletons/encryption_key.md | 18 - .../baseline-0.1.0/skeletons/jwt_claim.md | 27 - .../baseline-0.1.0/skeletons/mfa_method.md | 19 - .../skeletons/password_policy.md | 26 - .../baseline-0.1.0/skeletons/permission.md | 18 - .../baseline-0.1.0/skeletons/policy.md | 25 - .../fixtures/baseline-0.1.0/skeletons/risk.md | 20 - .../fixtures/baseline-0.1.0/skeletons/role.md | 21 - .../baseline-0.1.0/skeletons/scope.md | 20 - .../baseline-0.1.0/skeletons/secret.md | 25 - .../skeletons/session_config.md | 24 - .../baseline-0.1.0/skeletons/threat.md | 19 - .../skeletons/trust_boundary.md | 26 - .../baseline-0.1.0/skeletons/vulnerability.md | 21 - tests/test_additive_compatibility.py | 105 ---- tests/test_manifest.py | 2 +- tests/test_manifest_semantic.py | 51 +- tests/test_quoin_install_roundtrip.py | 3 +- tests/test_skeletons_semantic.py | 20 +- 39 files changed, 89 insertions(+), 1446 deletions(-) delete mode 100644 spec/non-functional/NFR-001-additive-compatibility.md delete mode 100644 spec/non-functional/index.md delete mode 100644 tests/fixtures/baseline-0.1.0/manifest.yaml delete mode 100644 tests/fixtures/baseline-0.1.0/skeletons/asset.md delete mode 100644 tests/fixtures/baseline-0.1.0/skeletons/attack_surface.md delete mode 100644 tests/fixtures/baseline-0.1.0/skeletons/audit_event.md delete mode 100644 tests/fixtures/baseline-0.1.0/skeletons/audit_finding.md delete mode 100644 tests/fixtures/baseline-0.1.0/skeletons/auth_flow.md delete mode 100644 tests/fixtures/baseline-0.1.0/skeletons/control.md delete mode 100644 tests/fixtures/baseline-0.1.0/skeletons/cors_policy.md delete mode 100644 tests/fixtures/baseline-0.1.0/skeletons/csrf_token.md delete mode 100644 tests/fixtures/baseline-0.1.0/skeletons/data_classification.md delete mode 100644 tests/fixtures/baseline-0.1.0/skeletons/encryption_key.md delete mode 100644 tests/fixtures/baseline-0.1.0/skeletons/jwt_claim.md delete mode 100644 tests/fixtures/baseline-0.1.0/skeletons/mfa_method.md delete mode 100644 tests/fixtures/baseline-0.1.0/skeletons/password_policy.md delete mode 100644 tests/fixtures/baseline-0.1.0/skeletons/permission.md delete mode 100644 tests/fixtures/baseline-0.1.0/skeletons/policy.md delete mode 100644 tests/fixtures/baseline-0.1.0/skeletons/risk.md delete mode 100644 tests/fixtures/baseline-0.1.0/skeletons/role.md delete mode 100644 tests/fixtures/baseline-0.1.0/skeletons/scope.md delete mode 100644 tests/fixtures/baseline-0.1.0/skeletons/secret.md delete mode 100644 tests/fixtures/baseline-0.1.0/skeletons/session_config.md delete mode 100644 tests/fixtures/baseline-0.1.0/skeletons/threat.md delete mode 100644 tests/fixtures/baseline-0.1.0/skeletons/trust_boundary.md delete mode 100644 tests/fixtures/baseline-0.1.0/skeletons/vulnerability.md delete mode 100644 tests/test_additive_compatibility.py diff --git a/scripts/generate-schemas.mjs b/scripts/generate-schemas.mjs index ebd5813..d49296f 100644 --- a/scripts/generate-schemas.mjs +++ b/scripts/generate-schemas.mjs @@ -28,7 +28,6 @@ import { dirname, join, relative, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const MIN_NODE_MAJOR = 20; -const SEMANTIC_CORE_BASE = "https://schemas.agent-ix.org/semantic-core/0.3.0/"; const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const sourceDir = resolve(repoRoot, "typespec"); @@ -45,12 +44,23 @@ function requireNode() { const major = Number(process.versions.node.split(".")[0]); if (!Number.isFinite(major) || major < MIN_NODE_MAJOR) { fail( - `Node ${MIN_NODE_MAJOR} or later is required by @typespec/compiler 1.15.0; this is Node ${process.versions.node}.`, + `Node ${MIN_NODE_MAJOR} or later is required; this is Node ${process.versions.node}.`, ); } } -/** The manifest `version`, read without a YAML parser so the file is never reserialized. */ +/** + * The semantic-core base this module extends: the one version the manifest + * declares as `semantic.semantic_core`, read without a YAML parser so the file + * is never reserialized. + */ +function semanticCoreBase() { + const manifest = readFileSync(resolve(packageDir, "manifest.yaml"), "utf8"); + const declared = manifest.match(/^[ ]{2}semantic_core:[ ]*([^\s#]+)[ ]*$/m)?.[1]; + if (!declared) fail("manifest.yaml declares no semantic.semantic_core"); + return `https://schemas.agent-ix.org/semantic-core/${declared}/`; +} + /** The `@jsonSchema` base declared by the source. */ function moduleBase() { const source = readFileSync(resolve(sourceDir, "main.tsp"), "utf8"); @@ -82,10 +92,10 @@ function compile(scratch) { * Rewrite a relative `$id`/`$ref` to an absolute one: a file this module emits * resolves under the module base, anything else under the semantic-core base. */ -function normalize(schemas, base, moduleFiles) { +function normalize(schemas, base, moduleFiles, coreBase) { const absolutize = (value) => { if (typeof value !== "string" || /^https?:\/\//.test(value)) return value; - return moduleFiles.has(value) ? `${base}${value}` : `${SEMANTIC_CORE_BASE}${value}`; + return moduleFiles.has(value) ? `${base}${value}` : `${coreBase}${value}`; }; const walk = (name, node) => { if (Array.isArray(node)) { @@ -128,7 +138,7 @@ function emit() { ); } const moduleFiles = new Set(mine.map(([name]) => name)); - normalize(mine, base, moduleFiles); + normalize(mine, base, moduleFiles, semanticCoreBase()); const rendered = new Map(mine.map(([name, schema]) => [name, render(schema)])); return rendered; diff --git a/spec/functional/FR-003-semantic-manifest-contract.md b/spec/functional/FR-003-semantic-manifest-contract.md index a4b864a..78d95b9 100644 --- a/spec/functional/FR-003-semantic-manifest-contract.md +++ b/spec/functional/FR-003-semantic-manifest-contract.md @@ -37,23 +37,17 @@ traceability rule keeps its meaning. ## Outputs -- `manifest.yaml` with `version: 0.2.0`, a `semantic` block, and reference-form +- `manifest.yaml` with a `version`, a `semantic` block, and reference-form `data_schema` on every exported object type. -- `tests/fixtures/baseline-0.1.0/`, the frozen 0.1.0 manifest and its - twenty-three skeletons. It is a deliverable of this requirement, not an - incidental copy: without it every locator, edge-vocabulary and additive - criterion compares the manifest against itself. ## Behavior -- The manifest `semantic` block SHALL carry exactly these keys and values: `contract_version: 1.0.0`, `semantic_core: 0.3.0`, `package: agent-ix/spec-objects-security`, `exports` listing every object type that ships a schema, `imports: {}`, `targets: [json-schema, markdown]`, `mappings: [typed-table, sysml-fence, ocl-clause]`, `compatibility_posture: additive`, `legacy_forms: warning`. +- The manifest `semantic` block SHALL carry exactly these keys and values: `contract_version: 1.0.0`, `semantic_core` (the one semantic-core version the module declares it extends), `package: agent-ix/spec-objects-security`, `exports` listing every object type that ships a schema, `imports: {}`, `targets: [json-schema, markdown]`, `mappings: [typed-table, sysml-fence, ocl-clause]`, `compatibility_posture: additive`, `legacy_forms: warning`. - `semantic.exports` SHALL name all twenty-three object types: `auth_flow`, `permission`, `scope`, `role`, `secret`, `encryption_key`, `session_config`, `data_classification`, `trust_boundary`, `audit_event`, `csrf_token`, `cors_policy`, `password_policy`, `mfa_method`, `jwt_claim`, `threat`, `control`, `risk`, `vulnerability`, `asset`, `attack_surface`, `policy`, `audit_finding`. - Every exported object type's `data_schema` SHALL be `{ schema: schemas/.json }`. - No exported object type SHALL carry an inline `data_schema`. -- Every `body_extraction` locator present at version 0.1.0 SHALL remain present with the same `from`, heading, `language`, `required`, `multiple`, and `assert` facets. -- The manifest SHALL carry the `traceability` block (`required_relations`, `acyclic_edges`) and every object type's `allowed_links` and `roles` unchanged. `agent-ix/spec-objects-safety` reads no field of this manifest today; what it shares is the `traceability` shape it mirrored from here and the verb `mitigates`, which `spec-artifacts-iso` FR-004 owns. The field with cross-repository consequence is `control.allowed_links.mitigates` — `[threat, risk, vulnerability]`, which excludes `hazard` and `failure_mode` — so changing it decides whether a security control can ever satisfy a safety coverage check. That is a cross-repository decision and is out of scope here. +- The manifest SHALL carry the `traceability` block (`required_relations`, `acyclic_edges`) and every object type's `allowed_links` and `roles`. `agent-ix/spec-objects-safety` reads no field of this manifest today; what it shares is the `traceability` shape it mirrored from here and the verb `mitigates`, which `spec-artifacts-iso` FR-004 owns. The field with cross-repository consequence is `control.allowed_links.mitigates` — `[threat, risk, vulnerability]`, which excludes `hazard` and `failure_mode` — so changing it decides whether a security control can ever satisfy a safety coverage check. That is a cross-repository decision and is out of scope here. - The `data_schema` reference form and the `semantic` block SHALL be judged by the consumer that reads them today: Quire's registry loader accepts the block and drops the module's object types when it is mutated (FR-003-AC-4, FR-003-AC-6). `quoin module install` (FR-003-AC-5) is a Demonstration this repository cannot run. No test here applies `filament-core-service`'s FR-035 schema as a document; that conformance is observed only at activation (FR-001-AC-2), which needs a running service. -- Where an object type gains a locator after 0.1.0, that locator SHALL be `required: false`, so existing artifacts stay valid (the additions themselves are specified by FR-005). - The manifest SHALL load through Quire's registry loader with no `ArchetypeLoadFailure` for any object type. - A refused schema drops that object type alone, while a manifest key the loader cannot parse (an unknown `semantic` key) drops every object type of the module, so a consumer sees the module as absent. Both refusals are silent — no diagnostic names the offending key or path — which `agent-ix/quire-rs#221` and `agent-ix/quire-rs#394` record as engine defects; the naming half of FR-003-AC-6 is blocked on them and is verified as an explicit expected failure rather than dropped. - The manifest SHALL install through `quoin module install path:` with no `semantic.*` error diagnostic. @@ -65,19 +59,15 @@ traceability rule keeps its meaning. | ID | Constraint | Type | Validation | |----|------------|------|------------| | FR-003-CON-1 | The `semantic` block SHALL contain no key outside the admitted list. Quire's loader refusal of an unknown key is verified here (FR-003-AC-6); Quoin's refusal is the neighbour's own obligation (quoin FR-070) and is assumed, evidenced only by the clean install of IT-002. | Compatibility | Test | -| FR-003-CON-2 | The manifest SHALL mark every locator added after 0.1.0 `required: false`. | Compatibility | Test | -| FR-003-CON-3 | The manifest SHALL keep the `traceability` block and every `allowed_links` verb and target list byte-identical to 0.1.0, so `agent-ix/spec-objects-safety`'s hazard-coverage edges are unaffected. | Boundary | Test | ## Acceptance Criteria | ID | Criteria | Verification | |----|----------|--------------| | FR-003-AC-1 | The loaded `semantic` block equals the nine admitted keys with the values above, and `exports` equals the twenty-three object-type names. | Test | -| FR-003-AC-3 | Every 0.1.0 locator, compared against the checked-in 0.1.0 baseline, is present unchanged; every added locator is `required: false`. | Test | | FR-003-AC-4 | `quire.Registry.load_from([module dir])` lists all twenty-three archetypes and `validate_document` on each skeleton reports no `semantic.*` load failure. | Test | | FR-003-AC-5 | `quoin module install path:` exits zero and `quoin module` lists `spec-objects-security`; the previously installed entry is restored afterwards. | Demonstration | | FR-003-AC-6 | A manifest copy whose `semantic` block gains a key `foo` is refused by Quire's loader. The refusal is verified; the half that requires the diagnostic to *name* `foo` or the path is an explicit expected failure while `agent-ix/quire-rs#221` and `agent-ix/quire-rs#394` are open. | Test | -| FR-003-AC-7 | The `traceability` block, the `lexicon`, and every `allowed_links` and `roles` map are equal to the checked-in 0.1.0 baseline. | Test | ## Dependencies diff --git a/spec/index.md b/spec/index.md index 5dff485..3496c42 100644 --- a/spec/index.md +++ b/spec/index.md @@ -9,7 +9,6 @@ okf_version: "0.1" ## Contents * [Functional](./functional/index.md) - Functional artifacts. -* [Non Functional](./non-functional/index.md) - Non-functional artifacts. * [Usecase](./usecase/index.md) - User story artifacts. * [Integration](./integration/index.md) - Integration artifacts. * [Stakeholder](./stakeholder/index.md) - Stakeholder artifacts. diff --git a/spec/integration/IT-002-quoin-module-install.md b/spec/integration/IT-002-quoin-module-install.md index b22a9ee..76ba5df 100644 --- a/spec/integration/IT-002-quoin-module-install.md +++ b/spec/integration/IT-002-quoin-module-install.md @@ -25,10 +25,9 @@ no network read is involved. ## Preconditions -A Quoin built from `agent-ix/quoin` main is on `PATH` -(from a checkout: `make build && npm i -g .`; no release tag carries the -semantic module yet). The current `quoin module` listing is recorded so the -prior `spec-objects-security` entry (source, ref, sha) can be restored. +A Quoin with the module installer is on `PATH`. The current `quoin module` +listing is recorded so the prior `spec-objects-security` entry can be +restored. ## Inputs diff --git a/spec/non-functional/NFR-001-additive-compatibility.md b/spec/non-functional/NFR-001-additive-compatibility.md deleted file mode 100644 index 4876c37..0000000 --- a/spec/non-functional/NFR-001-additive-compatibility.md +++ /dev/null @@ -1,97 +0,0 @@ ---- -id: NFR-001 -title: "Additive compatibility of the semantic contract" -type: NFR -quality_attribute: compatibility -relationships: - - target: "ix://agent-ix/spec-objects-security/FR-003" - type: "constrains" - - target: "ix://agent-ix/spec-objects-security/FR-004" - type: "constrains" - - target: "ix://agent-ix/spec-objects-security/FR-005" - type: "constrains" ---- -# NFR-001: Additive compatibility of the semantic contract - -## Statement - -The module SHALL keep every artifact of the checked-in 0.1.0 skeleton set — -the twenty-three skeletons as they stood at manifest version 0.1.0, which is -the population this NFR measures — validating against version 0.2.0 with at -most warning-level semantic findings. - -The module SHALL keep every 0.1.0 `body_extraction` locator definition -unchanged at 0.2.0, and SHALL keep the `traceability` block, the `lexicon`, and -every `allowed_links` and `roles` map equal to the frozen 0.1.0 baseline under -`tests/fixtures/baseline-0.1.0/`. Equality is asserted over the parsed -structures, which is what a consumer reads; byte-for-byte identity of the file -is neither asserted nor needed. Yields of the 0.1.0 locators other than the sections this change -adds are unmeasured and are not claimed. - -## Scope - -- Applies to: `manifest.yaml`, the shipped schemas, and the skeletons. -- Deliberately **not** measured: corpus artifacts outside this repository. At - 0.1.0 every type carried `data_schema: {type: object}`, so a corpus artifact - declaring `object: threat` was validated against nothing; at 0.2.0 it is - validated against a sealed schema and can newly fail - `semantic.record-invalid` at error severity, which `legacy_forms: warning` - does not soften. That population is real and is the reason the change is - advisory until corpus promotion (`agent-ix/quoin#291`); this module measures - only what it ships, and says so rather than implying a guarantee it never - tested. -- Operational context: existing corpus artifacts authored in legacy - Properties forms under `legacy_forms: warning`; no corpus repository is - edited. - -## Rationale - -The ticket's merge gate is advisory-only until corpus promotion. A module that -turned legacy artifacts into errors would force corpus edits this campaign -forbids; a module that changed a locator would change every existing -extraction record; a module that changed an edge verb would break a sibling -repository's coverage checks silently. - -## Measurement and Evaluation - -| Metric | Target | Threshold | Method | -|--------|--------|-----------|--------| -| 0.1.0 locators changed | 0 | 0 | Test | -| `traceability`, `lexicon`, `allowed_links`, `roles` entries changed | 0 | 0 | Test | -| Checked-in 0.1.0 skeleton set under 0.2.0: error findings, per skeleton | 0 | 0 | Test | -| Each 0.1.0 skeleton under 0.2.0: `semantic.record-invalid` findings | 0 | 0 | Test | - -## Verification - -NFR-001-AC-3 holds on the population this NFR measures, and the measurement -says why: no 0.1.0 skeleton carries a frontmatter `object:` key, so Quire runs -headings-only validation on it and never assembles or checks a typed record. -That is what makes 0.2.0 additive for the artifacts that exist today, and it -is asserted rather than assumed. - -The engine defect behind it is real but differently scoped: once a legacy-form -artifact *does* declare `object:`, quire assembles its declaration -record as `{}` and validates it against the type schema unconditionally, so it -fails `semantic.record-invalid` at error severity even under -`legacy_forms: warning`. `agent-ix/quire-rs#391` owns that rule. The module -carries that case as an explicit expected failure beside NFR-001-AC-4 rather -than relaxing a schema, so the day the engine changes, the row turns red and -is noticed. - -A checked-in copy of the 0.1.0 `body_extraction`, `traceability` and -`allowed_links` blocks, and of all twenty-three 0.1.0 skeletons, is compared -against the 0.2.0 manifest and validated under it. - -## Acceptance Criteria - -| ID | Criteria | Verification | -|----|----------|--------------| -| NFR-001-AC-1 | Every 0.1.0 `body_extraction` locator is present in 0.2.0 with identical facets (0 changed). | Test | -| NFR-001-AC-2 | The `traceability` block, the `lexicon`, and every object type's `allowed_links` and `roles` are equal to the frozen 0.1.0 baseline (0 changed). | Test | -| NFR-001-AC-3 | Every skeleton of the checked-in 0.1.0 set validates under 0.2.0 with 0 error findings. | Test | -| NFR-001-AC-4 | No skeleton of the checked-in 0.1.0 set yields a `semantic.record-invalid` finding under 0.2.0. | Test | - -## Dependencies - -- **Upstream**: [FR-003](../functional/FR-003-semantic-manifest-contract.md), [FR-005](../functional/FR-005-executable-skeletons.md); quoin FR-074 (`ix://agent-ix/quoin/FR-074`) -- **Downstream**: corpus promotion (`agent-ix/quoin#291` sweep), outside this module; `agent-ix/spec-objects-safety` hazard coverage, unaffected by design diff --git a/spec/non-functional/index.md b/spec/non-functional/index.md deleted file mode 100644 index 7a097cf..0000000 --- a/spec/non-functional/index.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -type: index -title: "Non Functional" -description: "Index of artifacts in this directory." ---- -# Non Functional - -## Contents - -* [NFR-001: Additive compatibility of the semantic contract](./NFR-001-additive-compatibility.md) diff --git a/spec/spec.md b/spec/spec.md index 5327a79..2c34f39 100644 --- a/spec/spec.md +++ b/spec/spec.md @@ -66,12 +66,7 @@ This document specifies the requirements for `spec-objects-security`, a Filament depends on is authorable nowhere: this module's `control` declares `mitigates: [threat, risk, vulnerability]`, and the `spec-artifacts-iso` `FR`/`NFR` archetypes declare no `mitigates` at all. The fix is a coordinated - change across at least two repositories and is not made here. Note the - interaction this ticket creates for whoever makes it: FR-003-CON-3 and - NFR-001-AC-2 now assert that list equal to the frozen 0.1.0 baseline, so the - coordinated change must update `tests/fixtures/baseline-0.1.0/manifest.yaml` - in the same commit or TC-047 and TC-101 turn red. That is the freeze working - as intended — the edge cannot move silently — not an obstacle to the fix. + change across at least two repositories and is not made here. - Reconciling this module's `Severity` and `Likelihood` vocabularies with `agent-ix/spec-objects-safety`'s `hazard-severity` and `hazard-likelihood` lint columns. The names collide and the member sets are disjoint; a shared @@ -107,19 +102,13 @@ This document specifies the requirements for `spec-objects-security`, a Filament manifest key empties the model silently) and `agent-ix/quire-rs#394`. FR-003-AC-6's "naming the key or the path" half is blocked on them and is carried as an explicit expected failure. -- Record validation of a legacy-form artifact that declares `object:`: - `agent-ix/quire-rs#391` (the engine validates an `unavailable` record as - `{}`, so a legacy form errors even under `legacy_forms: warning`). - NFR-001-AC-3 itself holds — no 0.1.0 artifact carries `object:` — and the - defect is carried as an explicit expected failure beside it rather than - worked around by relaxing a schema. - Resolving a reference-form `data_schema` into a stored snapshot at activation: `agent-ix/filament-core-service#23`. Until it lands the service stores the reference verbatim. - Editing any corpus repository or vendored fixture; the legacy-form sweep and corpus promotion (`agent-ix/quoin#291`). - Hosting the semantic-core schema bundle. Every module schema `$ref`s - `https://schemas.agent-ix.org/semantic-core/0.3.0/.json`, and nothing + `https://schemas.agent-ix.org/semantic-core//.json`, and nothing in this repository serves that host; a consumer resolves it from the `@agent-ix/semantic-core` package, and publishing the bundle is `agent-ix/filament-core-data#11`. The module's own tests resolve every such @@ -146,7 +135,6 @@ The specification is organized into the standard requirement classes, each in it - `stakeholder/` — StR-XXX stakeholder requirements. - `functional/` — FR-XXX functional requirements. - `integration/` — IT-XXX integration tests. -- `non-functional/` — NFR-XXX non-functional requirements. - `usecase/` — US-XXX user stories. - `tests.md` — the per-repo test matrix tracing functional requirements to their tests. @@ -154,7 +142,7 @@ FR-001 activates the manifest against `filament-core`; FR-002 emits the schemas; FR-003 declares the semantic contract in the manifest; FR-004 fixes each type's role-distinct schema; FR-005 makes the skeletons executable fixtures; FR-006 encodes the module's security rules into those schemas. -NFR-001 bounds the change to additive compatibility. Integration tests in +Integration tests in `integration/` verify the activation and Quoin-install boundaries; the third external boundary, the Quire engine (loader, extraction, record surface), has no IT artifact of its own — the FR-003, FR-005 and FR-006 test harness is this diff --git a/spec/tests.md b/spec/tests.md index ea89809..783f333 100644 --- a/spec/tests.md +++ b/spec/tests.md @@ -15,8 +15,6 @@ relationships: type: covers - target: "ix://agent-ix/spec-objects-security/FR-006" type: covers - - target: "ix://agent-ix/spec-objects-security/NFR-001" - type: covers --- # Test Matrix @@ -24,17 +22,16 @@ relationships: This matrix is the verification contract for the module: the manifest activation requirement (FR-001, issue #1 era, rows TC-001..TC-014) and the -issue #13 semantic module contract (US-001, FR-002..FR-006, NFR-001, IT-002). +issue #13 semantic module contract (US-001, FR-002..FR-006, IT-002). Coverage is complete when every acceptance criterion, named constraint, and NFR metric maps to at least one test case. Rows are `🚧` until a tagged test asserts them. The rows that stay `🚧` are the ones whose evidence needs an environment this repository cannot provision — a -running `filament-core-service`, a Quoin built from `quoin` main — or a human; +running `filament-core-service`, a Quoin with the module installer — or a human; every one of them says which. -`quire coverage --scope .` reported **131 of 146 rows backed (89%)** before PLAT-902 deleted -TC-005 and its two tests; the populations are now 145 and 85. The count is not +`quire coverage --scope .` reports a backed-row count. That count is not the rows of `## Test Case Summary`: a row there is one trace target, and so is every acceptance criterion, constraint and validation criterion a requirement document mints on its own, so the two numbers count @@ -43,14 +40,12 @@ source symbol outside `spec/` carries a matching tag; on its own it does not say the tagged test asserts the row, which is what the code review and the gap analysis are for. -The 15 it cannot back are exactly the fifteen targets below that read `🚧`: the +The targets it cannot back are exactly those below that read `🚧`: the nine rows TC-015..TC-019, TC-046, TC-110, TC-112 and TC-113, and the six criteria FR-001-AC-2..4, FR-003-AC-5 and StR-001-VC-1..2. Every one needs a -running `filament-core-service`, a Quoin built from `quoin` main, or a human. +running `filament-core-service`, a Quoin with the module installer, or a human. -`make test` reported **213 passed, 2 xfailed and 0 skipped** over the tree that -measurement was taken on; PLAT-902 removed two of those tests. -That counts pytest functions, not matrix rows, and the two numbers are not +`make test` counts pytest functions, not matrix rows, and the two numbers are not comparable. The 100% figure `make test` also prints is statement coverage over `spec_objects_security/__init__.py` — four statements — and is evidence about the Python package only: neither Node script is measured by it. @@ -85,7 +80,7 @@ the Python package only: neither Node script is measured by it. | User Story | Acceptance Criteria | Test Cases | Coverage Status | |---|---|---|---| -| US-001 | US-001-EX-1..3 (illustrative) implemented by FR-002..FR-006 | TC-080, TC-073, TC-110 | 🚧 TC-110 needs a Quoin built from quoin main | +| US-001 | US-001-EX-1..3 (illustrative) implemented by FR-002..FR-006 | TC-080, TC-073, TC-110 | 🚧 TC-110 needs a Quoin with the module installer | ### Functional Requirement Coverage @@ -93,23 +88,17 @@ the Python package only: neither Node script is measured by it. |---|---|---|---| | FR-001 | FR-001-AC-1..4 | TC-001..TC-004, TC-006..TC-014, TC-015..TC-017 | 🚧 AC-2..AC-4 need a running filament-core | | FR-002 | FR-002-AC-1..4, FR-002-AC-6..11, FR-002-CON-1..4 | TC-020..TC-035 | ✅ | -| FR-003 | FR-003-AC-1..7, FR-003-CON-1..3 | TC-040..TC-047 | ✅ AC-5 is a Demonstration nothing here can discharge; AC-6's naming half is an expected failure | +| FR-003 | FR-003-AC-1, FR-003-AC-4..6, FR-003-CON-1 | TC-040, TC-041, TC-044..TC-046 | ✅ AC-5 is a Demonstration nothing here can discharge; AC-6's naming half is an expected failure | | FR-004 | FR-004-AC-1..14, FR-004-CON-1..2 | TC-050..TC-063 | ✅ | | FR-005 | FR-005-AC-1..9, FR-005-CON-1..3 | TC-080..TC-090 | ✅ | | FR-006 | FR-006-AC-1..8, FR-006-CON-1..2 | TC-070..TC-078 | ✅ | -### Non-Functional Requirement Coverage - -| Non-Functional Req | Verification Method | Evidence/Test Cases | Status | -|---|---|---|---| -| NFR-001 | Test (NFR-001-AC-1..4: locator baseline diff, edge-vocabulary baseline diff, legacy skeleton validation) | TC-100..TC-103 | ✅ | - ### Integration Test Coverage | Integration Test | Success Criteria | Test Cases | Coverage Status | |---|---|---|---| | IT-001 | IT-001-AC-1..2 | TC-018, TC-019 | 🚧 needs a running filament-core | -| IT-002 | IT-002-SC-01..06 | TC-110 | 🚧 needs a Quoin built from main | +| IT-002 | IT-002-SC-01..06 | TC-110 | 🚧 needs a Quoin with the module installer | ## Test Case Summary @@ -145,12 +134,9 @@ the Python package only: neither Node script is measured by it. | TC-032 | `package-lock.json` resolves every package from npmjs except `@agent-ix/semantic-core` (GitHub Packages, `npm.pkg.github.com`) | Unit | P2 | FR-002-CON-4 | ✅ | | TC-035 | `.gitattributes` pins `eol=lf` and `npm pack` leaves nothing staged at the repository root | Unit | P1 | FR-002-AC-11 | ✅ | | TC-040 | The `semantic` block equals the nine admitted keys and `exports` equals the twenty-three types | Unit | P0 | FR-003-AC-1, FR-003-CON-1 | ✅ | -| TC-042 | Every 0.1.0 locator is unchanged against the checked-in baseline | Unit | P0 | FR-003-AC-3 | ✅ | -| TC-043 | Every added locator is `required: false` | Unit | P1 | FR-003-AC-3, FR-003-CON-2 | ✅ | | TC-044 | `quire.Registry.load_from` lists all twenty-three archetypes and `validate_document` reports no `semantic.*` load failure on any skeleton | Integration | P0 | FR-003-AC-4 | ✅ | | TC-045 | An unknown `semantic` key is refused by the loader; the refusal names the key or path | Integration | P1 | FR-003-AC-6 | ✅ refusal verified; the naming half is a strict expected failure on quire-rs#221 and quire-rs#394 | | TC-046 | `quoin module install path:` succeeds, lists the module, and the prior entry is restored | Manual | P1 | FR-003-AC-5 | 🚧 | -| TC-047 | The `traceability` block and every `allowed_links`/`roles` map equal the 0.1.0 baseline | Unit | P0 | FR-003-AC-7, FR-003-CON-3 | ✅ | | TC-050 | Each of the twenty-three schemas differs from every other in a required, admitted, or item rule; none is `type: object` only | Unit | P0 | FR-004-AC-1 | ✅ | | TC-051 | Asset: identity record validates; identity flag removed fails; no `fields` fails | Integration | P0 | FR-004-AC-2 | ✅ | | TC-052 | Role with `operations` fails; permission and scope with `relations` each fail | Integration | P0 | FR-004-AC-3 | ✅ | @@ -183,13 +169,9 @@ the Python package only: neither Node script is measured by it. | TC-086 | Every skeleton is placeholder-free with non-empty asserted sections | Unit | P2 | FR-005-AC-7 | ✅ | | TC-087 | Skeleton titles are distinct `Identifier`s outside `KernelScalar`, and `object` equals `type` in every skeleton frontmatter | Unit | P1 | FR-005-AC-8 | ✅ | | TC-088 | No skeleton or fixture matches a credential-shaped literal | Unit | P0 | FR-005-AC-9, FR-005-CON-3 | ✅ | -| TC-089 | No corpus repository or vendored fixture is edited by the change (diff over the branch) | Static | P2 | FR-005-CON-1 | ✅ | +| TC-089 | No corpus repository or vendored fixture is tracked (tree assertion over `git ls-files`) | Static | P2 | FR-005-CON-1 | ✅ | | TC-090 | A Properties section holding both a table and a fence is refused at the second form | Integration | P1 | FR-005-CON-2 | ✅ | -| TC-100 | Zero 0.1.0 locators changed | Unit | P0 | NFR-001-AC-1 | ✅ | -| TC-101 | Zero `traceability`/`allowed_links` bytes changed against the 0.1.0 baseline | Unit | P0 | NFR-001-AC-2 | ✅ | -| TC-102 | Every checked-in 0.1.0 skeleton validates under 0.2.0 with zero errors | Integration | P0 | NFR-001-AC-3 | ✅ | -| TC-103 | No checked-in 0.1.0 skeleton yields `semantic.record-invalid` under 0.2.0; the `object:`-declaring case is an expected failure | Integration | P0 | NFR-001-AC-4 | ✅ | -| TC-110 | Quoin install roundtrip with state restore | Manual | P1 | IT-002-SC-01..IT-002-SC-06, FR-003-AC-5 | 🚧 needs a Quoin built from quoin main (no release carries it) | +| TC-110 | Quoin install roundtrip with state restore | Manual | P1 | IT-002-SC-01..IT-002-SC-06, FR-003-AC-5 | 🚧 needs a Quoin with the module installer | | TC-111 | Every object type ships a typed schema a fixture reader can consume; a secret and a risk record are distinguishable by schema alone | Unit | P2 | StR-001-VC-3 | ✅ | | TC-112 | A module activation against filament-core registers every declared contribution | Manual | P2 | StR-001-VC-1 | 🚧 needs a running filament-core | | TC-113 | A generator run (minijinja-cli) produces a valid artifact from a shipped skeleton and schema | Manual | P2 | StR-001-VC-2 | 🚧 needs a generator run against a released module | @@ -213,17 +195,16 @@ say so; they are schema evidence, not extraction evidence. ## Coverage Gaps Every acceptance criterion, named constraint and NFR metric of FR-001..FR-006, -NFR-001, IT-001, IT-002 and StR-001 now has at least one row. Nine rows cannot +IT-001, IT-002 and StR-001 now has at least one row. Nine rows cannot be discharged in this repository and are `🚧` with the reason on the row: TC-015..TC-019 and TC-112 need a running `filament-core-service`, TC-046 and -TC-110 need a Quoin built from `quoin` main, and TC-113 +TC-110 need a Quoin with the module installer, and TC-113 needs a generator run against a released module. TC-114 is deliberately a row of its own rather than a tag on TC-110. A test that only checks the module directory is complete cannot back "the pre-install listing is captured", and tagging it `TC-110` made a blocked row report backed -— which the gap analysis caught. Splitting it moved the headline from 131/145 -to 131/146 with TC-110 now correctly unbacked, which is a truer number rather +— which the gap analysis caught. Splitting it left TC-110 correctly unbacked, which is a truer number rather than a better one. `quire coverage` additionally reports `status-column-matches-nothing` on the diff --git a/spec_objects_security/manifest.yaml b/spec_objects_security/manifest.yaml index e707f21..c7de516 100644 --- a/spec_objects_security/manifest.yaml +++ b/spec_objects_security/manifest.yaml @@ -78,7 +78,7 @@ object_types: after_heading: Flow required: true language: mermaid - # Added at 0.2.0 (FR-005): the `## Operations` subsections. + # FR-005: the `## Operations` subsections. operations: from: section_body after_heading: Operations @@ -106,8 +106,8 @@ object_types: path: - verb required: true - # Added at 0.2.0 (FR-005): the typed `## Properties` table the - # semantic layer reads. Optional, so every 0.1.0 artifact stays valid. + # FR-005: the typed `## Properties` table the + # semantic layer reads. Optional, so existing artifacts stay valid. properties: from: section_body after_heading: Properties @@ -127,8 +127,8 @@ object_types: from: section_body after_heading: Grants required: true - # Added at 0.2.0 (FR-005): the typed `## Properties` table the - # semantic layer reads. Optional, so every 0.1.0 artifact stays valid. + # FR-005: the typed `## Properties` table the + # semantic layer reads. Optional, so existing artifacts stay valid. properties: from: section_body after_heading: Properties @@ -149,8 +149,8 @@ object_types: from: section_body after_heading: Permissions required: true - # Added at 0.2.0 (FR-005): the typed `## Properties` table the - # semantic layer reads. Optional, so every 0.1.0 artifact stays valid. + # FR-005: the typed `## Properties` table the + # semantic layer reads. Optional, so existing artifacts stay valid. properties: from: section_body after_heading: Properties @@ -171,8 +171,8 @@ object_types: from: section_body after_heading: Rotation required: true - # Added at 0.2.0 (FR-005): the typed `## Properties` table the - # semantic layer reads. Optional, so every 0.1.0 artifact stays valid. + # FR-005: the typed `## Properties` table the + # semantic layer reads. Optional, so existing artifacts stay valid. properties: from: section_body after_heading: Properties @@ -199,8 +199,8 @@ object_types: path: - rotation required: false - # Added at 0.2.0 (FR-005): the typed `## Properties` table the - # semantic layer reads. Optional, so every 0.1.0 artifact stays valid. + # FR-005: the typed `## Properties` table the + # semantic layer reads. Optional, so existing artifacts stay valid. properties: from: section_body after_heading: Properties @@ -220,7 +220,7 @@ object_types: from: section_body after_heading: Settings required: true - # Added at 0.2.0 (FR-005): the `## Invariants` OCL clauses. + # FR-005: the `## Invariants` OCL clauses. invariants: from: section_body after_heading: Invariants @@ -241,8 +241,8 @@ object_types: from: section_body after_heading: Handling required: true - # Added at 0.2.0 (FR-005): the typed `## Properties` table the - # semantic layer reads. Optional, so every 0.1.0 artifact stays valid. + # FR-005: the typed `## Properties` table the + # semantic layer reads. Optional, so existing artifacts stay valid. properties: from: section_body after_heading: Properties @@ -264,13 +264,13 @@ object_types: after_heading: Boundary required: true language: mermaid - # Added at 0.2.0 (FR-005): the typed `## Properties` table the - # semantic layer reads. Optional, so every 0.1.0 artifact stays valid. + # FR-005: the typed `## Properties` table the + # semantic layer reads. Optional, so existing artifacts stay valid. properties: from: section_body after_heading: Properties required: false - # Added at 0.2.0 (FR-005): the `## Invariants` OCL clauses. + # FR-005: the `## Invariants` OCL clauses. invariants: from: section_body after_heading: Invariants @@ -292,8 +292,8 @@ object_types: after_heading: Event Schema required: true language: json - # Added at 0.2.0 (FR-005): the typed `## Properties` table the - # semantic layer reads. Optional, so every 0.1.0 artifact stays valid. + # FR-005: the typed `## Properties` table the + # semantic layer reads. Optional, so existing artifacts stay valid. properties: from: section_body after_heading: Properties @@ -315,8 +315,8 @@ object_types: path: - rotation_window required: true - # Added at 0.2.0 (FR-005): the typed `## Properties` table the - # semantic layer reads. Optional, so every 0.1.0 artifact stays valid. + # FR-005: the typed `## Properties` table the + # semantic layer reads. Optional, so existing artifacts stay valid. properties: from: section_body after_heading: Properties @@ -336,7 +336,7 @@ object_types: from: section_body after_heading: Origins required: true - # Added at 0.2.0 (FR-005): the `## Invariants` OCL clauses. + # FR-005: the `## Invariants` OCL clauses. invariants: from: section_body after_heading: Invariants @@ -356,7 +356,7 @@ object_types: from: section_body after_heading: Rules required: true - # Added at 0.2.0 (FR-005): the `## Invariants` OCL clauses. + # FR-005: the `## Invariants` OCL clauses. invariants: from: section_body after_heading: Invariants @@ -382,8 +382,8 @@ object_types: path: - factor required: false - # Added at 0.2.0 (FR-005): the typed `## Properties` table the - # semantic layer reads. Optional, so every 0.1.0 artifact stays valid. + # FR-005: the typed `## Properties` table the + # semantic layer reads. Optional, so existing artifacts stay valid. properties: from: section_body after_heading: Properties @@ -405,8 +405,8 @@ object_types: after_heading: Schema required: true language: json - # Added at 0.2.0 (FR-005): the typed `## Properties` table the - # semantic layer reads. Optional, so every 0.1.0 artifact stays valid. + # FR-005: the typed `## Properties` table the + # semantic layer reads. Optional, so existing artifacts stay valid. properties: from: section_body after_heading: Properties @@ -445,8 +445,8 @@ object_types: path: - vector required: true - # Added at 0.2.0 (FR-005): the typed `## Properties` table the - # semantic layer reads. Optional, so every 0.1.0 artifact stays valid. + # FR-005: the typed `## Properties` table the + # semantic layer reads. Optional, so existing artifacts stay valid. properties: from: section_body after_heading: Properties @@ -468,13 +468,13 @@ object_types: from: section_body after_heading: Mappings required: true - # Added at 0.2.0 (FR-005): the typed `## Properties` table the - # semantic layer reads. Optional, so every 0.1.0 artifact stays valid. + # FR-005: the typed `## Properties` table the + # semantic layer reads. Optional, so existing artifacts stay valid. properties: from: section_body after_heading: Properties required: false - # Added at 0.2.0 (FR-005): the `## Invariants` OCL clauses. + # FR-005: the `## Invariants` OCL clauses. invariants: from: section_body after_heading: Invariants @@ -501,8 +501,8 @@ object_types: path: - impact required: true - # Added at 0.2.0 (FR-005): the typed `## Properties` table the - # semantic layer reads. Optional, so every 0.1.0 artifact stays valid. + # FR-005: the typed `## Properties` table the + # semantic layer reads. Optional, so existing artifacts stay valid. properties: from: section_body after_heading: Properties @@ -529,8 +529,8 @@ object_types: path: - severity required: true - # Added at 0.2.0 (FR-005): the typed `## Properties` table the - # semantic layer reads. Optional, so every 0.1.0 artifact stays valid. + # FR-005: the typed `## Properties` table the + # semantic layer reads. Optional, so existing artifacts stay valid. properties: from: section_body after_heading: Properties @@ -550,8 +550,8 @@ object_types: from: section_body after_heading: Description required: true - # Added at 0.2.0 (FR-005): the typed `## Properties` table the - # semantic layer reads. Optional, so every 0.1.0 artifact stays valid. + # FR-005: the typed `## Properties` table the + # semantic layer reads. Optional, so existing artifacts stay valid. properties: from: section_body after_heading: Properties @@ -572,8 +572,8 @@ object_types: from: section_body after_heading: Entry Points required: true - # Added at 0.2.0 (FR-005): the typed `## Properties` table the - # semantic layer reads. Optional, so every 0.1.0 artifact stays valid. + # FR-005: the typed `## Properties` table the + # semantic layer reads. Optional, so existing artifacts stay valid. properties: from: section_body after_heading: Properties @@ -593,7 +593,7 @@ object_types: from: section_body after_heading: Policy required: true - # Added at 0.2.0 (FR-005): the `## Invariants` OCL clauses. + # FR-005: the `## Invariants` OCL clauses. invariants: from: section_body after_heading: Invariants @@ -614,15 +614,15 @@ object_types: from: section_body after_heading: Recommendation required: true - # Added at 0.2.0 (FR-005): the typed `## Properties` table the - # semantic layer reads. Optional, so every 0.1.0 artifact stays valid. + # FR-005: the typed `## Properties` table the + # semantic layer reads. Optional, so existing artifacts stay valid. properties: from: section_body after_heading: Properties required: false # FR-043: concrete-term lexicon — security / identity / auth objects. -# FR-058 (quire-rs v0.31.0): upward-trace completeness — the second half of +# FR-058 (quire-rs): upward-trace completeness — the second half of # agent-ix/spec-objects-security#5. Declaration, not code: the engine holds no # archetype name, no verb and no direction. # diff --git a/tests/conftest.py b/tests/conftest.py index 1021e64..4544c82 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -28,7 +28,6 @@ SCHEMAS_DIR = PACKAGE_ROOT / "schemas" SKELETONS_DIR = PACKAGE_ROOT / "skeletons" NEGATIVE_DIR = REPO_ROOT / "tests" / "fixtures" / "negative" -BASELINE_DIR = REPO_ROOT / "tests" / "fixtures" / "baseline-0.1.0" SEMANTIC_CORE_DIR = ( REPO_ROOT / "node_modules" diff --git a/tests/fixtures/baseline-0.1.0/manifest.yaml b/tests/fixtures/baseline-0.1.0/manifest.yaml deleted file mode 100644 index d6c686f..0000000 --- a/tests/fixtures/baseline-0.1.0/manifest.yaml +++ /dev/null @@ -1,509 +0,0 @@ -manifest_version: 1.0.0 -name: spec-objects-security -version: 0.1.0 -description: 'Filament Module: tier-2 security and identity ObjectTypes (threats, controls, auth flows, secrets, policies)' -nav: - category: - slug: security - label: Security - icon: ShieldCheck - order: 54 -archetypes: [] -grammars: [] -artifact_types: [] -lint_rules: [] -object_types: -- name: auth_flow - roles: [externally-exposed] - allowed_links: - requires: [mfa_method] - grants: [role] - protects: [externally-exposed] - references: [auth_flow] - data_schema: - type: object - body_extraction: - yield_pattern: - match: - id: &id001 - from: frontmatter_field - path: - - id - required: true - title: &id002 - from: frontmatter_field - path: - - title - required: true - type: &id003 - from: frontmatter_field - path: - - type - required: true - flow: - from: code_block - after_heading: Flow - required: true - language: mermaid -- name: permission - allowed_links: - grants: [scope] - guards: [externally-exposed] - references: [role] - data_schema: - type: object - body_extraction: - yield_pattern: - match: - id: *id001 - title: *id002 - type: *id003 - resource: - from: frontmatter_field - path: - - resource - required: true - verb: - from: frontmatter_field - path: - - verb - required: true -- name: scope - allowed_links: - references: [permission] - data_schema: - type: object - body_extraction: - yield_pattern: - match: - id: *id001 - title: *id002 - type: *id003 - grants: - from: section_body - after_heading: Grants - required: true -- name: role - allowed_links: - grants: [permission] - references: [role] - data_schema: - type: object - body_extraction: - yield_pattern: - match: - id: *id001 - title: *id002 - type: *id003 - permissions: - from: section_body - after_heading: Permissions - required: true -- name: secret - roles: [sensitive] - allowed_links: - references: [secret] - data_schema: - type: object - body_extraction: - yield_pattern: - match: - id: *id001 - title: *id002 - type: *id003 - rotation: - from: section_body - after_heading: Rotation - required: true -- name: encryption_key - roles: [sensitive] - allowed_links: - encrypts: [sensitive, data_classification] - data_schema: - type: object - body_extraction: - yield_pattern: - match: - id: *id001 - title: *id002 - type: *id003 - algorithm: - from: frontmatter_field - path: - - algorithm - required: true - rotation: - from: frontmatter_field - path: - - rotation - required: false -- name: session_config - allowed_links: - references: [auth_flow, jwt_claim] - data_schema: - type: object - body_extraction: - yield_pattern: - match: - id: *id001 - title: *id002 - type: *id003 - settings: - from: section_body - after_heading: Settings - required: true -- name: data_classification - allowed_links: - classifies: [persistable, externally-exposed, sensitive] - references: [data_classification] - data_schema: - type: object - body_extraction: - yield_pattern: - match: - id: *id001 - title: *id002 - type: *id003 - handling: - from: section_body - after_heading: Handling - required: true -- name: trust_boundary - allowed_links: - contains: [asset, api_endpoint] - references: [trust_boundary] - data_schema: - type: object - body_extraction: - yield_pattern: - match: - id: *id001 - title: *id002 - type: *id003 - boundary: - from: code_block - after_heading: Boundary - required: true - language: mermaid -- name: audit_event - roles: [event-like] - allowed_links: - references: [audit_event] - data_schema: - type: object - body_extraction: - yield_pattern: - match: - id: *id001 - title: *id002 - type: *id003 - event_schema: - from: code_block - after_heading: Event Schema - required: true - language: json -- name: csrf_token - roles: [sensitive] - allowed_links: - references: [csrf_token] - data_schema: - type: object - body_extraction: - yield_pattern: - match: - id: *id001 - title: *id002 - type: *id003 - rotation_window: - from: frontmatter_field - path: - - rotation_window - required: true -- name: cors_policy - allowed_links: - references: [cors_policy] - data_schema: - type: object - body_extraction: - yield_pattern: - match: - id: *id001 - title: *id002 - type: *id003 - origins: - from: section_body - after_heading: Origins - required: true -- name: password_policy - allowed_links: - references: [password_policy] - data_schema: - type: object - body_extraction: - yield_pattern: - match: - id: *id001 - title: *id002 - type: *id003 - rules: - from: section_body - after_heading: Rules - required: true -- name: mfa_method - allowed_links: - references: [mfa_method] - data_schema: - type: object - body_extraction: - yield_pattern: - match: - id: *id001 - title: *id002 - type: *id003 - kind: - from: frontmatter_field - path: - - kind - required: true - factor: - from: frontmatter_field - path: - - factor - required: false -- name: jwt_claim - roles: [sensitive] - allowed_links: - references: [jwt_claim] - data_schema: - type: object - body_extraction: - yield_pattern: - match: - id: *id001 - title: *id002 - type: *id003 - schema_json: - from: code_block - after_heading: Schema - required: true - language: json -- name: threat - allowed_links: - threatens: [asset, attack_surface] - exploits: [vulnerability] - data_schema: - type: object - body_extraction: - yield_pattern: - match: - id: &id004 - from: frontmatter_field - path: - - id - required: true - title: &id005 - from: frontmatter_field - path: - - title - required: true - type: &id006 - from: frontmatter_field - path: - - type - required: true - stride_category: - from: frontmatter_field - path: - - stride_category - required: true - vector: - from: frontmatter_field - path: - - vector - required: true -- name: control - allowed_links: - mitigates: [threat, risk, vulnerability] - implements: [policy] - protects: [externally-exposed, asset] - data_schema: - type: object - body_extraction: - yield_pattern: - match: - id: *id004 - title: *id005 - type: *id006 - mappings: - from: section_body - after_heading: Mappings - required: true -- name: risk - allowed_links: - arises_from: [threat, vulnerability] - references: [asset] - data_schema: - type: object - body_extraction: - yield_pattern: - match: - id: *id004 - title: *id005 - type: *id006 - likelihood: - from: frontmatter_field - path: - - likelihood - required: true - impact: - from: frontmatter_field - path: - - impact - required: true -- name: vulnerability - allowed_links: - affects: [asset, attack_surface] - references: [vulnerability] - data_schema: - type: object - body_extraction: - yield_pattern: - match: - id: *id004 - title: *id005 - type: *id006 - cve_id: - from: frontmatter_field - path: - - cve_id - required: false - severity: - from: frontmatter_field - path: - - severity - required: true -- name: asset - allowed_links: - references: [data_classification, trust_boundary] - data_schema: - type: object - body_extraction: - yield_pattern: - match: - id: *id004 - title: *id005 - type: *id006 - description_body: - from: section_body - after_heading: Description - required: true -- name: attack_surface - roles: [externally-exposed] - allowed_links: - exposes: [asset, api_endpoint] - data_schema: - type: object - body_extraction: - yield_pattern: - match: - id: *id004 - title: *id005 - type: *id006 - entry_points: - from: section_body - after_heading: Entry Points - required: true -- name: policy - allowed_links: - governs: [control, role, permission, data_classification] - data_schema: - type: object - body_extraction: - yield_pattern: - match: - id: *id004 - title: *id005 - type: *id006 - policy_body: - from: section_body - after_heading: Policy - required: true -- name: audit_finding - allowed_links: - traces_to: [risk] - references: [control, vulnerability, asset] - data_schema: - type: object - body_extraction: - yield_pattern: - match: - id: *id004 - title: *id005 - type: *id006 - recommendation: - from: section_body - after_heading: Recommendation - required: true - -# FR-043: concrete-term lexicon — security / identity / auth objects. -# FR-058 (quire-rs v0.31.0): upward-trace completeness — the second half of -# agent-ix/spec-objects-security#5. Declaration, not code: the engine holds no -# archetype name, no verb and no direction. -# -# A threat nobody controls and a risk nobody treats are both perfectly -# well-formed documents that pass every schema and grammar check, and both are -# evidence of an unaddressed exposure. Nothing that reads a document on its own -# can see it; only reading the edge set upward can. -traceability: - required_relations: - # STRIDE without coverage is a list. A threat with no inbound `mitigates` is - # one somebody enumerated and then left. - - name: threat-has-control - from: threat - edges: [mitigates] - to: [] - direction: incoming - check: uncontrolled-threat - # The same statement about the scored artefact rather than the mechanism: a - # risk with no treatment was assessed and then dropped. - - name: risk-has-treatment - from: risk - edges: [mitigates] - to: [] - direction: incoming - check: untreated-risk - # `direction: incoming` throughout, because the mitigation is authored from - # the control's or requirement's end — a control says what it mitigates. A - # threat listing its own controls would duplicate the fact and let the two - # drift. - # - # DELIBERATELY NOT DECLARED: `vulnerability`. #5 names "threat/hazard/risk" - # and a vulnerability is a property of an implementation rather than a thing - # the specification owes a response to. Adding it would widen the ticket. - # - # ALSO NOT DECLARED: the reverse direction, "every security-classified - # requirement traces to an upstream threat". It needs a way to say a - # requirement is security-classified, and no such axis exists — neither this - # module nor iso declares one. Filed rather than invented. - # - # A risk that transitively arises from itself states nothing, and no - # per-document check can see it: the defect is a property of the graph. - acyclic_edges: - - arises_from - -lexicon: - token: {definition: a bearer credential representing identity or grant} - secret: {definition: "a confidential value (key, password) kept out of code"} - scope: {definition: a named permission boundary on a grant} - claim: {definition: an assertion carried in a token} - permission: {definition: an allowed action on a resource} - credential: {definition: data proving an identity} - certificate: {definition: a signed binding of identity to a public key} - signature: {definition: a cryptographic proof of integrity/origin} - principal: {definition: an authenticated identity (user/service)} - keypair: {definition: a public/private cryptographic key pair} - nonce: {definition: a single-use value preventing replay} - hash: {definition: a fixed-size digest of input} - cipher: {definition: an encryption/decryption algorithm} - audit: {definition: "a recorded, tamper-evident event trail"} - policy: {definition: a declarative rule governing access or behavior} diff --git a/tests/fixtures/baseline-0.1.0/skeletons/asset.md b/tests/fixtures/baseline-0.1.0/skeletons/asset.md deleted file mode 100644 index 498c397..0000000 --- a/tests/fixtures/baseline-0.1.0/skeletons/asset.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -id: ASSET-001 -title: "Session store" -type: asset ---- - -# [ASSET-001] Session store - -Redis cluster holding all active Atlas web sessions and CSRF token state. - -## Description - -The session store is the authority for who is currently authenticated: -compromise yields the ability to mint or hijack any active session across all -tenants, bypassing AUTHFLOW-001 entirely. It runs in the trusted private -subnet behind BOUND-001, accepts only mTLS connections from the auth service, -and persists nothing to disk (RDB/AOF disabled) so session identifiers never -reach backup media. Data it holds is classified Confidential (CLASS-001 -handling applies to the embedded user identifiers). diff --git a/tests/fixtures/baseline-0.1.0/skeletons/attack_surface.md b/tests/fixtures/baseline-0.1.0/skeletons/attack_surface.md deleted file mode 100644 index 49bd616..0000000 --- a/tests/fixtures/baseline-0.1.0/skeletons/attack_surface.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -id: SURF-001 -title: "Atlas authentication attack surface" -type: attack_surface ---- - -# [SURF-001] Atlas authentication attack surface - -Internet-reachable surface of the Atlas authentication subsystem, reviewed -whenever a new route crosses BOUND-001 outward. - -## Entry Points - -- `GET /login`, `GET /callback` — browser OIDC endpoints (AUTHFLOW-001) -- `POST /api/token` — public API token exchange for integrations (SCOPE-001) -- `POST /auth/local` — break-glass local login, gated by PASSPOL-001 and - MFA-001 -- `POST /webhooks/idp` — IdP back-channel logout receiver, HMAC-verified -- Session cookie and `X-Atlas-CSRF` header parsing in the gateway middleware - (CSRF-001), reachable from any authenticated page diff --git a/tests/fixtures/baseline-0.1.0/skeletons/audit_event.md b/tests/fixtures/baseline-0.1.0/skeletons/audit_event.md deleted file mode 100644 index 8f5c4c3..0000000 --- a/tests/fixtures/baseline-0.1.0/skeletons/audit_event.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -id: AUDIT-001 -title: "user.login.succeeded audit event" -type: audit_event ---- - -# [AUDIT-001] user.login.succeeded audit event - -Emitted by the auth service when AUTHFLOW-001 completes and a session is -established. Appended to the tenant's immutable audit stream and queryable by -ROLE-001 (Tenant Auditor) holders. - -## Event Schema - -```json -{ - "type": "object", - "required": ["event", "occurred_at", "tenant_id", "user_id", "source_ip"], - "properties": { - "event": { "const": "user.login.succeeded" }, - "occurred_at": { "type": "string", "format": "date-time" }, - "tenant_id": { "type": "string", "format": "uuid" }, - "user_id": { "type": "string", "format": "uuid" }, - "source_ip": { "type": "string" }, - "idp_issuer": { "type": "string", "format": "uri" }, - "mfa_method": { "type": "string", "enum": ["totp", "webauthn", "none"] } - } -} -``` diff --git a/tests/fixtures/baseline-0.1.0/skeletons/audit_finding.md b/tests/fixtures/baseline-0.1.0/skeletons/audit_finding.md deleted file mode 100644 index badd59f..0000000 --- a/tests/fixtures/baseline-0.1.0/skeletons/audit_finding.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -id: FIND-001 -title: "Session store reachable with eval privileges" -type: audit_finding ---- - -# [FIND-001] Session store reachable with eval privileges - -Q2 security audit finding: the auth service connects to the session store -(ASSET-001) with a credential that retains `EVAL`/`SCRIPT` privileges, -unnecessarily exposing the Lua sandbox surface of VULN-001 to any service -compromise. Severity high; affected component is the shared Redis ACL profile. - -## Recommendation - -Upgrade the session store to a release patched for CVE-2024-31449, then -replace the shared ACL profile with a least-privilege profile that removes -`EVAL`, `EVALSHA` and `SCRIPT` for application credentials. Verify with an -integration test that script commands are rejected, and re-scan the cluster. -Target closure within 30 days; interim compensating control is the mTLS -restriction at BOUND-001. diff --git a/tests/fixtures/baseline-0.1.0/skeletons/auth_flow.md b/tests/fixtures/baseline-0.1.0/skeletons/auth_flow.md deleted file mode 100644 index 6d54523..0000000 --- a/tests/fixtures/baseline-0.1.0/skeletons/auth_flow.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -id: AUTHFLOW-001 -title: "Tenant user login via OIDC authorization code flow" -type: auth_flow ---- - -# [AUTHFLOW-001] Tenant user login via OIDC authorization code flow - -Interactive browser login for Atlas tenant users. The platform delegates -authentication to the tenant's OIDC identity provider using the authorization -code flow with PKCE, then establishes a first-party session cookie. - -## Flow - -```mermaid -sequenceDiagram - participant Browser - participant AtlasApp - participant IdP - Browser->>AtlasApp: GET /login - AtlasApp->>Browser: 302 to IdP authorize endpoint with PKCE challenge - Browser->>IdP: GET /authorize with client_id and code_challenge - IdP->>Browser: 302 back with authorization code - Browser->>AtlasApp: GET /callback with code and state - AtlasApp->>IdP: POST /token with code and code_verifier - IdP->>AtlasApp: ID token plus access and refresh tokens - AtlasApp->>Browser: Set __Host-atlas_session cookie and redirect -``` diff --git a/tests/fixtures/baseline-0.1.0/skeletons/control.md b/tests/fixtures/baseline-0.1.0/skeletons/control.md deleted file mode 100644 index 6174da4..0000000 --- a/tests/fixtures/baseline-0.1.0/skeletons/control.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -id: CTRL-001 -title: "Refresh token rotation with reuse detection" -type: control ---- - -# [CTRL-001] Refresh token rotation with reuse detection - -Every refresh-token exchange invalidates the presented token and issues a new -one bound to the same grant family. Presenting an already-rotated token marks -the whole family compromised: all descendants are revoked, the session is -terminated, and a `token.reuse.detected` audit event fires. - -## Mappings - -- Mitigates THREAT-001 (stolen refresh token replay) -- Reduces RISK-001 likelihood from medium to low -- Implements OAuth 2.0 Security BCP (RFC 9700) refresh-token rotation guidance -- Maps to OWASP ASVS v4 §3.5.2 and NIST SP 800-63B §7.1 session binding -- Verified by integration test IT-001 (manifest activation roundtrip suite) diff --git a/tests/fixtures/baseline-0.1.0/skeletons/cors_policy.md b/tests/fixtures/baseline-0.1.0/skeletons/cors_policy.md deleted file mode 100644 index 90c4cd8..0000000 --- a/tests/fixtures/baseline-0.1.0/skeletons/cors_policy.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -id: CORS-001 -title: "Atlas public API CORS policy" -type: cors_policy ---- - -# [CORS-001] Atlas public API CORS policy - -Browser cross-origin policy enforced by the API gateway for `api.atlas.example`. -Credentials are only honored for first-party app origins; integration origins -get token-based access without cookies. - -## Origins - -- `https://app.atlas.example` — first-party web client; credentials allowed -- `https://admin.atlas.example` — operator console; credentials allowed -- `https://*.integrations.atlas.example` — sandboxed integration iframes; - credentials disallowed, `Authorization` header only -- All other origins are denied; no wildcard `*` is ever emitted, and preflight - responses are capped at `Access-Control-Max-Age: 600` diff --git a/tests/fixtures/baseline-0.1.0/skeletons/csrf_token.md b/tests/fixtures/baseline-0.1.0/skeletons/csrf_token.md deleted file mode 100644 index d92281c..0000000 --- a/tests/fixtures/baseline-0.1.0/skeletons/csrf_token.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -id: CSRF-001 -title: "Atlas web CSRF token" -type: csrf_token -rotation_window: 30m ---- - -# [CSRF-001] Atlas web CSRF token - -Double-submit CSRF token protecting all state-changing requests from the Atlas -web client. The server issues an HMAC-signed token bound to the session id -(SESSION-001); the client echoes it in the `X-Atlas-CSRF` header and the -middleware rejects any mutation where header and cookie values disagree. A -fresh token is minted every `30m` rotation window and on every login or -privilege elevation, with the previous token honored for one overlapping -window to avoid breaking in-flight forms. diff --git a/tests/fixtures/baseline-0.1.0/skeletons/data_classification.md b/tests/fixtures/baseline-0.1.0/skeletons/data_classification.md deleted file mode 100644 index 36679b7..0000000 --- a/tests/fixtures/baseline-0.1.0/skeletons/data_classification.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -id: CLASS-001 -title: "Confidential — tenant PII" -type: data_classification ---- - -# [CLASS-001] Confidential — tenant PII - -Classification applied to tenant member profile data (names, email addresses, -phone numbers) and any document flagged as containing personal data. Sits one -level below `Restricted` (secrets and key material) in the Atlas scheme. - -## Handling - -- Encrypted at rest with the tenant DEK (KEY-001) and in transit via TLS 1.3. -- Access requires an explicit permission grant; reads are recorded in the - audit stream as `pii.accessed` events. -- Never written to application logs; log scrubbing rejects payloads matching - the PII field allowlist. -- Exported only through the GDPR export pipeline, with tenant-admin approval. -- Retention: deleted within 30 days of tenant offboarding or subject request. diff --git a/tests/fixtures/baseline-0.1.0/skeletons/encryption_key.md b/tests/fixtures/baseline-0.1.0/skeletons/encryption_key.md deleted file mode 100644 index a85a116..0000000 --- a/tests/fixtures/baseline-0.1.0/skeletons/encryption_key.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -id: KEY-001 -title: "Tenant data encryption key" -type: encryption_key -algorithm: AES-256-GCM -rotation: 90d ---- - -# [KEY-001] Tenant data encryption key - -Per-tenant data encryption key (DEK) protecting documents at rest in the Atlas -object store. Each DEK is wrapped by the platform KMS root key and unwrapped -only inside the storage service; plaintext keys never leave process memory. -Rotation re-wraps the DEK on the `90d` cadence declared above without -re-encrypting historical objects, which remain readable via key versioning. diff --git a/tests/fixtures/baseline-0.1.0/skeletons/jwt_claim.md b/tests/fixtures/baseline-0.1.0/skeletons/jwt_claim.md deleted file mode 100644 index 6d286f7..0000000 --- a/tests/fixtures/baseline-0.1.0/skeletons/jwt_claim.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -id: CLAIM-001 -title: "tenant_id access-token claim" -type: jwt_claim ---- - -# [CLAIM-001] tenant_id access-token claim - -Custom claim stamped into every Atlas access token at issuance. The policy -engine uses it for row-level tenant isolation (see PERM-001); tokens without a -valid `tenant_id` are rejected at the gateway before reaching any service. - -## Schema - -```json -{ - "title": "tenant_id", - "type": "string", - "format": "uuid", - "description": "UUID of the tenant the subject is acting within; immutable for the token lifetime" -} -``` diff --git a/tests/fixtures/baseline-0.1.0/skeletons/mfa_method.md b/tests/fixtures/baseline-0.1.0/skeletons/mfa_method.md deleted file mode 100644 index 003459a..0000000 --- a/tests/fixtures/baseline-0.1.0/skeletons/mfa_method.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -id: MFA-001 -title: "TOTP authenticator app" -type: mfa_method -kind: totp -factor: possession ---- - -# [MFA-001] TOTP authenticator app - -Time-based one-time password second factor (RFC 6238) for Atlas operator and -break-glass accounts. Secrets are 160-bit, provisioned via QR code over an -authenticated session, and stored encrypted under KEY-001. Codes are 6 digits -on a 30-second step with one step of clock skew accepted; ten single-use -recovery codes are issued at enrollment for device-loss fallback. diff --git a/tests/fixtures/baseline-0.1.0/skeletons/password_policy.md b/tests/fixtures/baseline-0.1.0/skeletons/password_policy.md deleted file mode 100644 index e04804e..0000000 --- a/tests/fixtures/baseline-0.1.0/skeletons/password_policy.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -id: PASSPOL-001 -title: "Atlas local-account password policy" -type: password_policy ---- - -# [PASSPOL-001] Atlas local-account password policy - -Applies to break-glass local accounts only; regular tenant users authenticate -through their IdP (AUTHFLOW-001). Aligned with NIST SP 800-63B: length over -composition, breach screening over forced rotation. - -## Rules - -- Minimum length 12 characters, maximum 128; all printable Unicode accepted -- Candidate passwords are checked against the haveibeenpwned k-anonymity API - and rejected when previously breached -- No mandatory periodic rotation; rotation is forced only on suspected - compromise -- Hashing: argon2id with memory 64 MiB, iterations 3, parallelism 4 -- Rate limiting: 10 failed attempts per account per 15 minutes, then lockout - with audit event emission diff --git a/tests/fixtures/baseline-0.1.0/skeletons/permission.md b/tests/fixtures/baseline-0.1.0/skeletons/permission.md deleted file mode 100644 index 9000217..0000000 --- a/tests/fixtures/baseline-0.1.0/skeletons/permission.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -id: PERM-001 -title: "Read tenant documents" -type: permission -resource: document -verb: read ---- - -# [PERM-001] Read tenant documents - -Grants read access to `document` resources within the caller's tenant. Checked -by the Atlas policy engine on every `GET /api/documents/*` request; the -permission is satisfied only when the resource's `tenant_id` matches the -caller's `tenant_id` claim. diff --git a/tests/fixtures/baseline-0.1.0/skeletons/policy.md b/tests/fixtures/baseline-0.1.0/skeletons/policy.md deleted file mode 100644 index 570eb68..0000000 --- a/tests/fixtures/baseline-0.1.0/skeletons/policy.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -id: POL-001 -title: "Access token lifetime policy" -type: policy ---- - -# [POL-001] Access token lifetime policy - -Platform-wide policy governing bearer token lifetimes for Atlas first-party -and integration clients, owned by the security council. - -## Policy - -- Access tokens SHALL expire no later than 15 minutes after issuance. -- Refresh tokens SHALL be single-use and rotated on every exchange (CTRL-001), - with an absolute family lifetime of 30 days. -- Integration tokens issued for SCOPE-001 SHALL be bound to the requesting - client via DPoP or mTLS sender constraint. -- Token lifetimes SHALL NOT be extended per tenant without a documented risk - acceptance referencing the affected risk register entries (e.g. RISK-001). -- Violations surface as audit findings (FIND-001 family) and block release - until remediated or formally accepted. diff --git a/tests/fixtures/baseline-0.1.0/skeletons/risk.md b/tests/fixtures/baseline-0.1.0/skeletons/risk.md deleted file mode 100644 index 1f3e2e7..0000000 --- a/tests/fixtures/baseline-0.1.0/skeletons/risk.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -id: RISK-001 -title: "Tenant account takeover via token theft" -type: risk -likelihood: medium -impact: high ---- - -# [RISK-001] Tenant account takeover via token theft - -Business risk that an attacker realizes THREAT-001 and operates inside a -tenant with the victim's permissions, exposing Confidential PII (CLASS-001) -and enabling data exfiltration through the public API. Likelihood is rated -medium given commodity infostealer malware; impact is high because a single -takeover crosses into regulated personal data. Primary treatment is CTRL-001; -residual risk after rotation and reuse detection is accepted at low/high and -reviewed quarterly by the security council. diff --git a/tests/fixtures/baseline-0.1.0/skeletons/role.md b/tests/fixtures/baseline-0.1.0/skeletons/role.md deleted file mode 100644 index 7546e8f..0000000 --- a/tests/fixtures/baseline-0.1.0/skeletons/role.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -id: ROLE-001 -title: "Tenant Auditor role" -type: role ---- - -# [ROLE-001] Tenant Auditor role - -Read-only compliance role assigned to external auditors invited into a tenant. -Members can inspect configuration and audit trails but can never mutate state -or read secret material. - -## Permissions - -- PERM-001 — `document:read` within the tenant -- `audit_log:read` — query the tenant's audit event stream -- `policy:read` — view security policies in effect for the tenant -- `member:list` — enumerate tenant members and their role bindings diff --git a/tests/fixtures/baseline-0.1.0/skeletons/scope.md b/tests/fixtures/baseline-0.1.0/skeletons/scope.md deleted file mode 100644 index 493a5f9..0000000 --- a/tests/fixtures/baseline-0.1.0/skeletons/scope.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -id: SCOPE-001 -title: "documents:read OAuth scope" -type: scope ---- - -# [SCOPE-001] documents:read OAuth scope - -OAuth 2.0 scope requested by third-party integrations that need read-only -access to a tenant's document library through the Atlas public API. - -## Grants - -- PERM-001 — read tenant documents (`document:read`) -- Listing document metadata via `GET /api/documents` -- Downloading rendered document content via `GET /api/documents/{id}/content` -- Excludes all write verbs; mutations require the separate `documents:write` scope diff --git a/tests/fixtures/baseline-0.1.0/skeletons/secret.md b/tests/fixtures/baseline-0.1.0/skeletons/secret.md deleted file mode 100644 index 3c3b67e..0000000 --- a/tests/fixtures/baseline-0.1.0/skeletons/secret.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -id: SECRET-001 -title: "Atlas OIDC client secret" -type: secret ---- - -# [SECRET-001] Atlas OIDC client secret - -Confidential client secret used by the Atlas backend to authenticate at the -tenant IdP token endpoint during the authorization code exchange. Stored in -Vault at `secret/atlas/oidc/client-secret`; never present in environment files -or images. - -## Rotation - -- Rotated every 90 days by the `secret-rotator` job, which registers a new - secret with the IdP before revoking the old one. -- Dual-secret overlap window of 24 hours so in-flight token exchanges complete - against either value. -- Emergency rotation is triggered manually via `atlasctl secrets rotate - oidc-client` and completes in under 5 minutes. -- Every rotation emits an `secret.rotated` audit event (AUDIT-001 family). diff --git a/tests/fixtures/baseline-0.1.0/skeletons/session_config.md b/tests/fixtures/baseline-0.1.0/skeletons/session_config.md deleted file mode 100644 index ae2ce5f..0000000 --- a/tests/fixtures/baseline-0.1.0/skeletons/session_config.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -id: SESSION-001 -title: "Atlas web session configuration" -type: session_config ---- - -# [SESSION-001] Atlas web session configuration - -Server-side session established after AUTHFLOW-001 completes. Session state -lives in the session store (ASSET-001); the browser holds only an opaque -identifier. - -## Settings - -- Cookie name: `__Host-atlas_session` (Secure, HttpOnly, SameSite=Lax, Path=/) -- Idle timeout: 30 minutes, sliding on authenticated requests -- Absolute lifetime: 12 hours, after which re-authentication is required -- Session identifier: 256-bit random value, regenerated on login and on - privilege elevation -- Concurrent session cap: 5 per user; the oldest session is evicted on overflow diff --git a/tests/fixtures/baseline-0.1.0/skeletons/threat.md b/tests/fixtures/baseline-0.1.0/skeletons/threat.md deleted file mode 100644 index 8325edf..0000000 --- a/tests/fixtures/baseline-0.1.0/skeletons/threat.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -id: THREAT-001 -title: "Stolen refresh token replay" -type: threat -stride_category: Spoofing -vector: "exfiltrated refresh token replayed from attacker infrastructure" ---- - -# [THREAT-001] Stolen refresh token replay - -An attacker who exfiltrates a refresh token (malware on the user device, log -leakage, or proxy compromise) replays it against the token endpoint to mint -fresh access tokens and impersonate the user across tenant APIs. Detection is -hard because requests are well-formed; mitigation relies on refresh token -rotation with reuse detection (CTRL-001) and the trust boundary controls in -BOUND-001. Realized risk is tracked as RISK-001. diff --git a/tests/fixtures/baseline-0.1.0/skeletons/trust_boundary.md b/tests/fixtures/baseline-0.1.0/skeletons/trust_boundary.md deleted file mode 100644 index 167df7e..0000000 --- a/tests/fixtures/baseline-0.1.0/skeletons/trust_boundary.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -id: BOUND-001 -title: "Edge to auth-service trust boundary" -type: trust_boundary ---- - -# [BOUND-001] Edge to auth-service trust boundary - -Separates the untrusted internet, the DMZ-hosted API gateway, and the trusted -private subnet where the Atlas auth service and session store run. Everything -crossing inward is re-authenticated: the gateway terminates TLS and the auth -service only accepts mTLS connections from the gateway. - -## Boundary - -```mermaid -flowchart LR - Browser["Tenant browser (untrusted)"] -->|HTTPS| Gateway["API gateway (DMZ)"] - Gateway -->|mTLS| AuthSvc["Auth service (trusted subnet)"] - AuthSvc -->|private VPC peering| SessionDB[("Session store")] -``` diff --git a/tests/fixtures/baseline-0.1.0/skeletons/vulnerability.md b/tests/fixtures/baseline-0.1.0/skeletons/vulnerability.md deleted file mode 100644 index 7e99d7a..0000000 --- a/tests/fixtures/baseline-0.1.0/skeletons/vulnerability.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -id: VULN-001 -title: "Session store affected by lua sandbox escape" -type: vulnerability -cve_id: CVE-2024-31449 -severity: high ---- - -# [VULN-001] Session store affected by lua sandbox escape - -The Redis instance backing the Atlas session store (ASSET-001) runs a version -affected by CVE-2024-31449, where an authenticated client can craft a Lua -script that escapes the sandbox and executes arbitrary code. Exploitation -requires script-eval privileges inside the private subnet, so exposure is -limited by BOUND-001, but a compromised service credential would yield full -session-store control. Remediation: upgrade to a patched Redis release and -disable `EVAL` for application credentials; tracked to closure in FIND-001. diff --git a/tests/test_additive_compatibility.py b/tests/test_additive_compatibility.py deleted file mode 100644 index 4e7b7bc..0000000 --- a/tests/test_additive_compatibility.py +++ /dev/null @@ -1,105 +0,0 @@ -"""The previous population still validates under the new manifest version. - -The population is named: the twenty-three skeletons as they stood at manifest -version 0.1.0, frozen under `tests/fixtures/baseline-0.1.0/`. That freeze is -this requirement's evidence and is compared against the live manifest, so a -locator or edge-vocabulary change cannot pass unnoticed. -""" - -from __future__ import annotations - -import pytest -import yaml - -from tests.conftest import ( - BASELINE_DIR, - OBJECT_TYPES, - PACKAGE_ROOT, - frontmatter, - load_manifest, - locators, - object_type, -) - -BASELINE_MANIFEST = yaml.safe_load((BASELINE_DIR / "manifest.yaml").read_text()) -BASELINE_SKELETONS = sorted((BASELINE_DIR / "skeletons").glob("*.md")) - - -def baseline_object_type(name: str) -> dict: - return next(ot for ot in BASELINE_MANIFEST["object_types"] if ot["name"] == name) - - -def test_the_frozen_baseline_is_the_previous_module_version(): - """The baseline is a deliverable, not an incidental copy: without it every - NFR-001 criterion compares the manifest against itself.""" - assert BASELINE_MANIFEST["version"] == "0.1.0" - assert len(BASELINE_SKELETONS) == 23 - assert {ot["name"] for ot in BASELINE_MANIFEST["object_types"]} == set(OBJECT_TYPES) - assert load_manifest()["version"] == "0.2.0" - - -@pytest.mark.trace("TC-100", "NFR-001-AC-1") -def test_zero_baseline_locators_changed(): - changed = [] - for name in OBJECT_TYPES: - before = locators(baseline_object_type(name)) - after = locators(object_type(name)) - for key, definition in before.items(): - if after.get(key) != definition: - changed.append((name, key)) - assert changed == [], changed - - -@pytest.mark.trace("TC-101", "NFR-001-AC-2") -def test_zero_traceability_or_edge_vocabulary_bytes_changed(): - current = load_manifest() - assert current["traceability"] == BASELINE_MANIFEST["traceability"] - assert current["lexicon"] == BASELINE_MANIFEST["lexicon"] - for name in OBJECT_TYPES: - before = baseline_object_type(name) - after = object_type(name) - assert after.get("allowed_links") == before.get("allowed_links"), name - assert after.get("roles") == before.get("roles"), name - - -@pytest.mark.trace("TC-102", "NFR-001-AC-3") -def test_every_baseline_skeleton_validates_under_the_new_manifest(quire_engine): - for path in BASELINE_SKELETONS: - text = path.read_text() - result = quire_engine.validate_document( - frontmatter(text)["type"], str(PACKAGE_ROOT), text - ) - assert result["is_valid"], (path.name, result["errors"]) - - -@pytest.mark.trace("TC-103", "NFR-001-AC-4") -def test_no_baseline_skeleton_yields_a_record_invalid_finding(quire_engine): - """It holds because no 0.1.0 skeleton declares frontmatter `object:`, so - Quire runs headings-only validation and never assembles a typed record. - That is asserted here rather than assumed.""" - for path in BASELINE_SKELETONS: - text = path.read_text() - assert "object" not in frontmatter(text), path.name - result = quire_engine.validate_document( - frontmatter(text)["type"], str(PACKAGE_ROOT), text - ) - assert not [ - e for e in result["errors"] if "semantic.record-invalid" in e["message"] - ], (path.name, result["errors"]) - - -@pytest.mark.xfail(strict=True, reason="agent-ix/quire-rs#391") -@pytest.mark.trace("TC-103", "NFR-001-AC-4") -def test_a_legacy_form_declaring_object_is_not_an_error(quire_engine): - """The engine defect NFR-001 carries rather than works around: once a - legacy-form artifact declares `object:`, quire assembles its record - as `{}` and validates it unconditionally, so it fails - `semantic.record-invalid` at error severity even under - `legacy_forms: warning`. Strict xfail, so the day the engine changes this - row turns red and is noticed.""" - path = BASELINE_DIR / "skeletons" / "asset.md" - text = path.read_text().replace("type: asset\n", "type: asset\nobject: asset\n", 1) - result = quire_engine.validate_document("asset", str(PACKAGE_ROOT), text) - assert not [ - e for e in result["errors"] if "semantic.record-invalid" in e["message"] - ], result["errors"] diff --git a/tests/test_manifest.py b/tests/test_manifest.py index 7a090e2..4970d2d 100644 --- a/tests/test_manifest.py +++ b/tests/test_manifest.py @@ -84,7 +84,7 @@ def test_lexicon_entries_are_whole() -> None: def test_threat_and_risk_coverage_is_declared_not_coded() -> None: """TC-007: agent-ix/spec-objects-security#5: upward coverage is manifest data. - Assumptions: quire-rs FR-058 (v0.31.0) reads + Assumptions: quire-rs FR-058 reads ``traceability.required_relations``; the engine holds no archetype name, no verb and no direction. diff --git a/tests/test_manifest_semantic.py b/tests/test_manifest_semantic.py index c900127..82f5bde 100644 --- a/tests/test_manifest_semantic.py +++ b/tests/test_manifest_semantic.py @@ -1,5 +1,4 @@ -"""The `semantic` block, the reference-form `data_schema`, and the frozen -locator and edge vocabulary.""" +"""The `semantic` block and the reference-form `data_schema`.""" from __future__ import annotations @@ -11,7 +10,6 @@ import yaml from tests.conftest import ( - BASELINE_DIR, MODEL_OF, OBJECT_TYPES, PACKAGE_ROOT, @@ -19,9 +17,6 @@ SCHEMAS_DIR, SKELETONS_DIR, frontmatter, - load_manifest, - locators, - object_type, ) ADMITTED_KEYS = { @@ -36,12 +31,6 @@ "legacy_forms", } -BASELINE_MANIFEST = yaml.safe_load((BASELINE_DIR / "manifest.yaml").read_text()) - - -def baseline_object_type(name: str) -> dict: - return next(ot for ot in BASELINE_MANIFEST["object_types"] if ot["name"] == name) - @pytest.mark.trace("TC-040", "FR-003-AC-1", "FR-003-CON-1") def test_the_semantic_block_is_exactly_the_nine_admitted_keys(semantic_block): @@ -58,44 +47,6 @@ def test_the_semantic_block_is_exactly_the_nine_admitted_keys(semantic_block): assert len(semantic_block["exports"]) == 23 -@pytest.mark.trace("TC-042", "FR-003-AC-3") -def test_every_baseline_locator_is_unchanged(): - for name in OBJECT_TYPES: - before = locators(baseline_object_type(name)) - after = locators(object_type(name)) - for key, definition in before.items(): - assert key in after, f"{name}: locator {key} disappeared" - assert after[key] == definition, f"{name}: locator {key} changed" - - -@pytest.mark.trace("TC-043", "FR-003-AC-3", "FR-003-CON-2") -def test_every_added_locator_is_optional(): - added = 0 - for name in OBJECT_TYPES: - before = locators(baseline_object_type(name)) - after = locators(object_type(name)) - for key, definition in after.items(): - if key in before: - continue - added += 1 - assert definition["required"] is False, f"{name}: {key} is required" - assert added == 25, added - - -@pytest.mark.trace("TC-047", "FR-003-AC-7", "FR-003-CON-3") -def test_the_edge_vocabulary_and_traceability_are_frozen(): - """The sibling `agent-ix/spec-objects-safety` declares its bidirectional - hazard coverage against these fields; changing one here is a - cross-repository contract change, not a module edit.""" - current = load_manifest() - assert current["traceability"] == BASELINE_MANIFEST["traceability"] - for name in OBJECT_TYPES: - before = baseline_object_type(name) - after = object_type(name) - assert after.get("allowed_links") == before.get("allowed_links"), name - assert after.get("roles") == before.get("roles"), name - - @pytest.mark.trace("TC-044", "FR-003-AC-4") def test_the_registry_lists_every_archetype_and_no_skeleton_fails_to_load( quire_engine, diff --git a/tests/test_quoin_install_roundtrip.py b/tests/test_quoin_install_roundtrip.py index cae792c..121796d 100644 --- a/tests/test_quoin_install_roundtrip.py +++ b/tests/test_quoin_install_roundtrip.py @@ -1,8 +1,7 @@ """The Quoin install roundtrip. TC-110 and its IT-002 success criteria stay `Manual` and `🚧`: they need a -Quoin built from `agent-ix/quoin` main, and no release -tag carries the semantic module installer. This file does **not** bind them — +Quoin that provides the module installer. This file does **not** bind them — a test that only checks files exist cannot claim "the pre-install listing is captured". What it binds is TC-114, its own row: the module directory the roundtrip would hand to Quoin is complete and self-consistent, so the manual diff --git a/tests/test_skeletons_semantic.py b/tests/test_skeletons_semantic.py index 2042c75..95c45b6 100644 --- a/tests/test_skeletons_semantic.py +++ b/tests/test_skeletons_semantic.py @@ -275,23 +275,9 @@ def test_no_skeleton_or_fixture_carries_a_credential_shaped_literal(): @pytest.mark.trace("TC-089", "FR-005-CON-1") def test_the_repository_holds_no_corpus_or_vendored_fixture(): - """FR-005-CON-1 as a **tree** assertion, never as a diff against a moving ref. - - The obvious form — `git diff --name-only origin/main...HEAD`, then check no - changed path is under `corpus/` or a vendor tree — degrades the moment the - branch merges. `origin/main...HEAD` then resolves to the empty set, so the - loops iterate over nothing and the guard passes while checking nothing; the - positive-diff variant of the same guard (`assert changed`) goes one worse - and turns main red for a branch that is no longer a branch. A merged - change's path set is a fixed historical fact, and an assertion about it must - not be computed against a ref that keeps moving. - - The tree form says something stronger and merge-invariant: these paths are - absent from the repository at all, not merely untouched by one branch. It is - exactly equivalent in intent here because no `corpus/`, - `fixtures/semantic-module` or vendor path exists in this repository, on this - branch or on `main`. No `git diff` survives in this guard, so there is no - range whose rename detection could hide a deletion. + """FR-005-CON-1 as a **tree** assertion: no `corpus/`, `fixtures/semantic-module` + or vendor path is tracked anywhere in this repository. `git ls-files` + answers the same question on any branch. """ listing = subprocess.run( ["git", "ls-files"],