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
44 changes: 44 additions & 0 deletions spec/fidelity-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Fidelity Model

How faithfully each carried unit must survive the carry, and how that is verified. This is a hub-only doc governing the carrying machinery ([`spec/files.json`][files], [`spec/files.schema.json`][files-schema], [`spec/audit.py`][audit]) and is not carried to the fleet. It is the companion to [`spec/scope-model.md`][scope-model]: scope decides *which* repos get a unit, fidelity decides *how faithfully* they must carry it.

## The Fixed and the Overridable

Carried content is a class with virtual functions. The **fixed** part is the interface - when a thing is invoked, what it is named, and where it is wired. The **overridable** part is the implementation body, which a repo replaces to fit its own targets. Validation must allow the override while detecting a change to the interface or to content meant to stay fixed. Integrity is by **content hash, never a version number** - a version stamp is a claim a repo can keep while editing the body, so it is never trusted for detection.

## The Four Fidelity Levels

Each [`spec/files.json`][files] entry declares one `fidelity`, defaulting to `presence`.

- **presence** - the unit exists (a file, or a markdown section heading). The audit's baseline check.
- **intent** - carried faithfully but judged by meaning, not bytes. A downstream copy legitimately differs (a governed divergence or a paraphrase), and equivalence is a human call via `intentRef`. The audit asserts nothing beyond presence.
- **verbatim** - byte-identical to the hub's canonical after declared-placeholder normalization. The audit content-hashes the downstream copy against canonical. It applies to a whole file or a stable-handle region (a markdown section by heading, a workflow job by key).
- **interface** - an overridable body that must honor a named contract. The audit checks the contract by name and wiring, never the body.

Fidelity is a declared field defaulting to `presence`, never inferred from `whole`/`placeholders`. `.editorconfig` and `.markdownlint-cli2.jsonc` are both whole with no placeholders yet sit at opposite fidelity, because the discriminator is governance, not field shape.

## Why Each Unit Sits Where It Does

- **verbatim** - `.markdownlint-cli2.jsonc` (fleet-generic, no governed divergence), and the `github-release` job region of the release task (the canonical orchestration a repo must not fork).
- **interface** - the release and PR workflows. Their fixed contract is the job and check names plus the artifact handoff, while the leaf build jobs are owned. See the override seam in [`AGENTS.md`][agents].
- **intent** - `.editorconfig` and `.gitattributes` (the `[*] end_of_line` default and path pins vary by platform), `cspell.json` (the words list and file scope vary), `CODESTYLE.md` / `WORKFLOW.md` / `AUDIT.md` / `.github/copilot-instructions.md` (carried docs judged by meaning), and the ruleset payloads (whose live state is diffed separately).
- **presence** - `README.md`, `HISTORY.md`, `.gitignore`, and the per-repo config that only needs to exist.

## The Workflow Override Seam Contract

The fixed interface of a workflow is stated in [`AGENTS.md`][agents] ("Orchestration vs. build - the override seam" and "Workflow YAML Conventions"), and the `interface` check enforces it by name and structure: the ruleset-bound required check `name: Check pull request workflow status job`, the `github-release` and `get-version` job keys, the `release-asset-<branch>-<target>` artifact-name handoff, and that `github-release` collects assets by `pattern:` / `merge-multiple:` and never by an `artifact-ids:` that names a build job's output. A repo owns the leaf `build-<target>-task` job list, its `needs` targets, and its paths-filter, and none of those are checked.

## Placeholder Normalization

A verbatim check normalizes only the tokens a unit **declares** in its `placeholders` list, never a blanket `<...>` regex. The declared tokens are literal strings (for example `<owner>`, `<repo>`, `<N>`), so masking touches exactly those and leaves intact the sibling metavariables a doc uses in prose (for example `<PATH>`, `<SHA>`). Line endings are neutralized before hashing, because EOL variance is governed by the line-ending rules, not a fidelity deviation.

## Stale Versus Violated

A verbatim mismatch is one of two things, told apart **by hash, not by a version**. The audit hashes each past revision of the hub's canonical from its own git history. If the downstream copy matches a **past** canonical revision, the base advanced and the copy is **stale** - re-vendor it. If it matches **no** revision the base ever produced, the repo **modified fixed content** - review it. A version stamp could claim to be current while being neither, so it is demoted to a human-facing label and never consulted for integrity.

<!-- Repo -->
[agents]: ../AGENTS.md
[audit]: ./audit.py
[files]: ./files.json
[files-schema]: ./files.schema.json
[scope-model]: ./scope-model.md
32 changes: 16 additions & 16 deletions spec/files.json
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
{
"$schema": "./files.schema.json",
"note": "The standardization baseline: files and sections a fleet repo is expected to carry, and their intent authority. The audit checks presence (letter) and equivalence (intent); a section for an absent language or target is N/A. Each entry, and each section, carries an appliesTo selector - see spec/scope-model.md for the scope model and selector vocabulary.",
"note": "The standardization baseline: files and sections a fleet repo is expected to carry, and their intent authority. The audit mechanically checks presence (letter). Equivalence (intent) is judged by hand, and a section for an absent language or target is N/A. Each entry, and each section, carries an appliesTo selector - see spec/scope-model.md for the scope model and selector vocabulary. Each entry also has a fidelity (presence by default, or intent, verbatim, interface) governing how faithfully the content is checked - see spec/fidelity-model.md.",
"baseline": [
{ "path": "AGENTS.md", "sections": ["Repository Boundaries and Write Safety", "Git and Commit Rules", "Branching Model", "Release Model", { "name": "Operational Repositories", "appliesTo": ["operational"] }, "Pull Request Title and Commit Message Conventions", "Documentation Style Conventions", "Verification Discipline", "PR Review Etiquette", "Workflow YAML Conventions"], "intentRef": "AGENTS.md", "appliesTo": "*" },
{ "path": "CODESTYLE.md", "whole": true, "placeholders": ["InternalsVisibleTo project names"], "intentRef": "CODESTYLE.md", "appliesTo": "*" },
{ "path": "WORKFLOW.md", "whole": true, "intentRef": "WORKFLOW.md", "appliesTo": "*" },
{ "path": "AGENTS.md", "fidelity": "intent", "sections": ["Repository Boundaries and Write Safety", "Git and Commit Rules", "Branching Model", "Release Model", { "name": "Operational Repositories", "appliesTo": ["operational"] }, "Pull Request Title and Commit Message Conventions", "Documentation Style Conventions", "Verification Discipline", "PR Review Etiquette", "Workflow YAML Conventions"], "intentRef": "AGENTS.md", "appliesTo": "*" },
{ "path": "CODESTYLE.md", "fidelity": "intent", "whole": true, "placeholders": ["InternalsVisibleTo project names"], "intentRef": "CODESTYLE.md", "appliesTo": "*" },
{ "path": "WORKFLOW.md", "fidelity": "intent", "whole": true, "intentRef": "WORKFLOW.md", "appliesTo": "*" },
{ "path": "README.md", "appliesTo": "*" },
{ "path": "HISTORY.md", "appliesTo": "*" },
{ "path": ".github/copilot-instructions.md", "whole": true, "placeholders": ["<owner>", "<repo>", "<N>"], "appliesTo": "*" },
{ "path": ".editorconfig", "whole": true, "intentRef": "AGENTS.md#line-endings", "appliesTo": "*" },
{ "path": ".gitattributes", "whole": true, "intentRef": "AGENTS.md#line-endings", "appliesTo": "*" },
{ "path": ".markdownlint-cli2.jsonc", "whole": true, "appliesTo": "*" },
{ "path": "cspell.json", "whole": true, "appliesTo": "*" },
{ "path": ".github/copilot-instructions.md", "fidelity": "intent", "whole": true, "placeholders": ["<owner>", "<repo>", "<N>"], "appliesTo": "*" },
{ "path": ".editorconfig", "fidelity": "intent", "whole": true, "intentRef": "AGENTS.md#line-endings", "appliesTo": "*" },
{ "path": ".gitattributes", "fidelity": "intent", "whole": true, "intentRef": "AGENTS.md#line-endings", "appliesTo": "*" },
{ "path": ".markdownlint-cli2.jsonc", "fidelity": "verbatim", "whole": true, "appliesTo": "*" },
{ "path": "cspell.json", "fidelity": "intent", "whole": true, "appliesTo": "*" },
{ "path": ".gitignore", "appliesTo": "*" },
{ "path": "version.json", "intentRef": "WORKFLOW.md#d3---versioning-and-classification", "appliesTo": "*" },
{ "path": "repo-config/develop.json", "intentRef": "repo-config/README.md", "appliesTo": ["release"] },
{ "path": "repo-config/operational/develop.json", "intentRef": "repo-config/README.md", "appliesTo": ["operational"] },
{ "path": "repo-config/main.json", "intentRef": "repo-config/README.md", "appliesTo": "*" },
{ "path": "AUDIT.md", "intentRef": "docs/repo-config-carry.md", "appliesTo": "*" },
{ "path": "spec/secrets.json", "intentRef": "docs/repo-config-carry.md", "appliesTo": "*" },
{ "path": "version.json", "fidelity": "intent", "intentRef": "WORKFLOW.md#d3---versioning-and-classification", "appliesTo": "*" },
{ "path": "repo-config/develop.json", "fidelity": "intent", "intentRef": "repo-config/README.md", "appliesTo": ["release"] },
{ "path": "repo-config/operational/develop.json", "fidelity": "intent", "intentRef": "repo-config/README.md", "appliesTo": ["operational"] },
{ "path": "repo-config/main.json", "fidelity": "intent", "intentRef": "repo-config/README.md", "appliesTo": "*" },
{ "path": "AUDIT.md", "fidelity": "intent", "intentRef": "docs/repo-config-carry.md", "appliesTo": "*" },
{ "path": "spec/secrets.json", "fidelity": "intent", "intentRef": "docs/repo-config-carry.md", "appliesTo": "*" },
{ "path": ".github/dependabot.yml", "appliesTo": "*" },
{ "path": ".vscode/tasks.json", "sections": ["clean-compile task group"], "reference": "catalog/snippets/configs/vscode-tasks.json", "appliesTo": ["csharp"] },
{ "path": ".vscode/tasks.json", "sections": ["clean-compile task group"], "reference": "catalog/snippets/configs/vscode-tasks-python.json", "appliesTo": ["python"] },
{ "path": "codecov.yml", "reference": "catalog/snippets/configs/codecov.yml", "intentRef": "WORKFLOW.md", "appliesTo": ["csharp", "python"] },
{ "path": "codecov.yml", "fidelity": "intent", "reference": "catalog/snippets/configs/codecov.yml", "intentRef": "WORKFLOW.md", "appliesTo": ["csharp", "python"] },
{ "path": ".dockerignore", "appliesTo": ["docker"] },
{ "path": "Docker/README.md", "reference": "catalog/snippets/configs/docker-hub-readme.md", "appliesTo": ["docker"] }
]
Expand Down
15 changes: 14 additions & 1 deletion spec/files.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,20 @@
"placeholders": { "type": "array", "items": { "type": "string" } },
"reference": { "type": "string" },
"intentRef": { "type": "string" },
"appliesTo": { "type": ["string", "array"], "items": { "type": "string" }, "minItems": 1 }
"appliesTo": { "type": ["string", "array"], "items": { "type": "string" }, "minItems": 1 },
"fidelity": { "enum": ["presence", "intent", "verbatim", "interface"] },
"contract": {
"type": "object",
"additionalProperties": false,
"properties": {
"requiredJobKeys": { "type": "array", "items": { "type": "string" } },
"requiredCheckName": { "type": "string" },
"artifactNameToken": { "type": "string" },
"requireTokensInJob": { "type": "object", "additionalProperties": { "type": "array", "items": { "type": "string" } } },
"forbidTokensInJob": { "type": "object", "additionalProperties": { "type": "array", "items": { "type": "string" } } },
"verbatimJobs": { "type": "array", "items": { "type": "string" } }
}
}
}
}
}
Expand Down
55 changes: 54 additions & 1 deletion spec/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,20 @@
WORKFLOW_MODELS = ("release", "operational")
RELEASE_TRIGGERS = ("two-phase", "publish-on-merge", "dispatch-only", "none")
CONSUMER_MODELS = ("push", "pull")
# How faithfully a carried unit is checked (spec/fidelity-model.md). Default presence.
FIDELITIES = ("presence", "intent", "verbatim", "interface")
# The keys an interface unit's `contract` may carry (kept in sync with files.schema.json).
CONTRACT_KEYS = {"requiredJobKeys", "requiredCheckName", "artifactNameToken", "requireTokensInJob", "forbidTokensInJob", "verbatimJobs"}


def load(rel):
return json.loads((ROOT / rel).read_text(encoding="utf-8"))


def is_str_list(v):
return isinstance(v, list) and all(isinstance(x, str) for x in v)


def main():
errors = []
repos = load("registry/repos.json")
Expand Down Expand Up @@ -224,8 +232,53 @@ def check_selector(where, applies_to):
if not isinstance(item, dict):
errors.append(f"files.json: baseline entry {item!r} is not an object")
continue
path = item.get("path", "?")
path = item.get("path")
if not isinstance(path, str):
errors.append(f"files.json: baseline entry has a missing or non-string path: {item!r}")
continue
check_selector(path, item.get("appliesTo", "*"))

# fidelity governs how faithfully the unit is checked (spec/fidelity-model.md). CI runs no schema
# validation, so shape-check the fidelity fields here rather than let a malformed contract or an
# outside-root reference slip through and crash a later check.
fid = item.get("fidelity", "presence")
if fid not in FIDELITIES:
errors.append(f"files.json: {path} fidelity '{fid}' invalid (expected one of {', '.join(FIDELITIES)})")
has_contract = "contract" in item
if has_contract and fid != "interface":
errors.append(f"files.json: {path} has a contract but fidelity is '{fid}' (contract is only for fidelity 'interface')")
if fid == "interface" and not has_contract:
errors.append(f"files.json: {path} fidelity 'interface' requires a contract")
if has_contract:
contract = item["contract"]
if not isinstance(contract, dict):
errors.append(f"files.json: {path} contract must be an object")
else:
unknown = set(contract) - CONTRACT_KEYS
if unknown:
errors.append(f"files.json: {path} contract has unknown key(s): {', '.join(sorted(unknown))}")
# The engine trusts these value types (CI runs no schema validation), so verify them here.
for k in ("requiredJobKeys", "verbatimJobs"):
if k in contract and not is_str_list(contract[k]):
errors.append(f"files.json: {path} contract.{k} must be an array of strings")
for k in ("requiredCheckName", "artifactNameToken"):
if k in contract and not isinstance(contract[k], str):
errors.append(f"files.json: {path} contract.{k} must be a string")
for k in ("requireTokensInJob", "forbidTokensInJob"):
v = contract.get(k)
if k in contract and not (isinstance(v, dict) and all(isinstance(j, str) and is_str_list(t) for j, t in v.items())):
errors.append(f"files.json: {path} contract.{k} must be an object of job name to array of strings")
ref = item.get("reference")
if ref is not None and not isinstance(ref, str):
errors.append(f"files.json: {path} reference must be a string")
ref = None
elif isinstance(ref, str) and (ref.startswith("/") or ".." in pathlib.PurePosixPath(ref).parts):
errors.append(f"files.json: {path} reference '{ref}' must be a repo-relative path (no leading / or ..)")
if fid == "verbatim":
src = ref if isinstance(ref, str) else path
if isinstance(src, str) and not (ROOT / src).exists():
errors.append(f"files.json: {path} fidelity 'verbatim' but its canonical source {src} is missing")

sections = item.get("sections", [])
if not isinstance(sections, list):
errors.append(f"files.json: {path} sections must be an array")
Expand Down