Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ make build # build wheel and source distribution
make lint # Ruff
make typecheck # strict mypy
make test # unit test suite
make catalog # static security catalog tests and validation
make smoke # API and worker process smoke suite
make check # all required repository checks
```
Expand Down
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: install build lint typecheck test smoke check
.PHONY: install build lint typecheck test catalog smoke check

install:
uv sync --frozen
Expand All @@ -15,7 +15,11 @@ typecheck:
test:
uv run pytest -q tests/unit

catalog:
uv run pytest -q tests/catalog
uv run python scripts/validate_security_catalog.py

smoke:
uv run pytest -q tests/process

check: build lint typecheck test smoke
check: build lint typecheck test catalog smoke
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ make build # 构建 wheel 和 sdist
make lint # Ruff
make typecheck # strict mypy
make test # 单元测试
make catalog # 安全目录静态测试与校验
make smoke # API / worker 进程 smoke
make check # build + lint + typecheck + test + smoke
make check # build + lint + typecheck + test + catalog + smoke
```

本地启动 API:
Expand Down
119 changes: 119 additions & 0 deletions docs/decisions/0019-security-catalog-normalization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
---
name: adr-0019-security-catalog-normalization
version: "1.0.0"
description: >
Normalize the release security catalog to fifteen stable invariant IDs and
distinguish canonical acceptance scenarios from derived evidence cases.
---

# 0019. Normalize the release security catalog to fifteen stable IDs

- Status: accepted
- Date: 2026-07-20

## Context

The security checklist, test architecture, and historical design accumulated
overlapping labels for the same release vetoes. Counting those labels as
independent invariant families produced a nineteen-entry prose list, while an
older acceptance section promoted ten later parameterizations into additional
top-level scenarios. Neither expansion added a new security boundary, but both
made release reports and milestone exits ambiguous.

The hard oracles, sealed authorization ordering, trusted delivery construction,
audience intersection, ACL-proof behavior, revocation behavior, and single
release owner are already fixed by the
[threat model](../security/context-engine-threat-model.md),
[ADR-0003](0003-group-chat-intersection-authorization.md),
[ADR-0010](0010-policy-epoch-revocation.md),
[ADR-0012](0012-sealed-authorization-projection-pipeline.md),
[ADR-0013](0013-trusted-delivery-egress-and-capability-taxonomy.md),
[ADR-0014](0014-curation-snapshot-and-release-ownership.md), and
[ADR-0017](0017-trusted-invocation-and-closed-runtime-access.md). Catalog
normalization must not weaken or renumber those safeguards.

## Decision

The canonical release catalog contains exactly these fifteen stable IDs, in
this order:

1. `TENANT-OWNERSHIP-001`
2. `TENANT-FK-002`
3. `RLS-FAIL-CLOSED-003`
4. `SCOPE-INTERSECTION-004`
5. `INDEX-NOT-AUTHORITY-005`
6. `REVOCATION-006`
7. `WORKER-LEASE-007`
8. `TRANSPORT-UNTRUSTED-008`
9. `NON-ENUMERATION-009`
10. `CITATION-AUTH-010`
11. `EGRESS-011`
12. `TRACE-REDACTION-012`
13. `ACTION-SEPARATION-014`
14. `CROSS-ORG-LEARN-015`
15. `RELEASE-OWNER-019`

`eval/catalogs/security-invariants.yaml` is the machine authority for this
set. `eval/catalogs/security-catalog.schema.json` validates its shape, and
`python3 scripts/validate_security_catalog.py` validates the catalog and its
tracked document references. The catalog uses JSON-compatible YAML so the D0
validator remains standard-library-only and deterministic in bootstrap and CI,
independent of application dependencies.

The following labels retain all of their tests and safeguards but are not
additional canonical release IDs:

- `AUDIENCE-016` is covered by `SCOPE-INTERSECTION-004` plus `EGRESS-011`.
- `ACL-PROOF-017` is covered by `INDEX-NOT-AUTHORITY-005` plus
`REVOCATION-006`.
- `DELIVERY-EVIDENCE-018` is covered by `TRANSPORT-UNTRUSTED-008`.

`CACHE-SCOPE-013` remains a preregistered conditional extension outside the
canonical fifteen. It becomes applicable with the first authorization-sensitive
final `ContextPackage` or `AuthorizedProjection` cache. Activating that
capability requires a future versioned catalog and schema change that adds the
extension and its proving cases; until then, composition tests must prove that
no such cache is active. Existing numbering is never reused or shifted.

The canonical V1 acceptance fixture has twelve top-level scenarios:
`ACCEPT-001` cross-Organization isolation (one fixture with bidirectional A/B
assertions), `ACCEPT-002` same-Organization Membership isolation,
`ACCEPT-003` Agent ceiling, `ACCEPT-004` request narrowing, `ACCEPT-005`
revocation, `ACCEPT-006` hostile index, `ACCEPT-007` transport injection,
`ACCEPT-008` WorkerLease replay/binding, `ACCEPT-009` source-native ACL,
`ACCEPT-010` citation revocation, `ACCEPT-011` denied/not-found equivalence,
and `ACCEPT-012` Context/Action separation. Cases historically numbered 13
through 22 remain required parameterized or derived cases mapped to those
twelve scenarios or directly to invariant evidence. They are not ten
additional top-level acceptance IDs.

## Rationale

One stable machine-readable set makes release completeness mechanically
checkable and prevents prose counts from becoming a second authority. Absorbing
overlapping labels preserves their negative cases while making each release
veto independently reportable. Keeping the cache rule conditional avoids
claiming an inactive cache capability while ensuring its security gate is
defined before activation.

## Consequences

Security documentation and generated reports must use the exact fifteen IDs
and may not describe the absorbed labels as extra families. Every absorbed or
derived case still needs evidence under its mapped canonical invariant. A
validator failure, unmapped active case, or missing evidence is a release
failure; normalization cannot turn it into `NOT_ACTIVE` or
`NOT_APPLICABLE`.

Reviewers can compare release reports without ID churn. Adding a genuinely new
security boundary requires an explicit catalog/schema version change and an
ADR; it cannot be introduced by silently extending a prose table.

## Revisit trigger

Reopen when a new implemented security boundary cannot be represented by the
canonical fifteen, or when an authorization-sensitive final
`ContextPackage`/`AuthorizedProjection` cache is first activated. Any revision
must preserve existing IDs and evidence history, name the new proving seam and
milestone applicability, update the versioned machine catalog and schema, and
retain the three hard zero oracles.
2 changes: 2 additions & 0 deletions docs/decisions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ kernel, capability separation, and publication visibility model.
| Trusted access boundary | [0017 — Trusted invocation and closed Runtime access](0017-trusted-invocation-and-closed-runtime-access.md) | HTTP, generated SDK, and activated MCP map to one Runtime contract; trusted inputs are ingress-built | Caller-supplied identity/ACL/audience, transport-local policy, or IM as a fourth transport |
| Read versus effect | [0011 — Read/write plane separation](0011-read-write-plane-separation.md) | `ContextAccessTicket` and `ActionTicket` use different audiences and are non-interchangeable | Using content/read authority to execute an external effect |
| Publication visibility | [0018 — Immutable ContextRevision publication](0018-immutable-revision-publication.md) | `ContextResource` content is immutable `ContextRevision`/`ContextFragment` lineage; one transaction changes the active pointer | In-place content mutation, mixed old/new reads, or cleanup-defined visibility |
| Release security catalog | [0019 — Security catalog normalization](0019-security-catalog-normalization.md) | One machine catalog contains exactly fifteen stable release IDs; overlapping labels and derived scenarios keep their safeguards without inflating the count | Parallel prose catalogs, renumbering, or treating inactive cache behavior as a canonical release family |

Each baseline ADR is `accepted` and contains Context, Decision, Rationale,
Consequences, and Revisit trigger sections. A revisit trigger permits review; it
Expand Down Expand Up @@ -87,3 +88,4 @@ touched:
- [0014 — Curation snapshot and release ownership](0014-curation-snapshot-and-release-ownership.md)
- [0015 — RLS transaction context and schema manifest](0015-rls-transaction-context-and-schema-manifest.md)
- [0016 — Implementation authority and vertical-slice roadmap](0016-implementation-authority-and-vertical-slice-roadmap.md)
- [0019 — Security catalog normalization](0019-security-catalog-normalization.md)
Loading
Loading