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
5 changes: 3 additions & 2 deletions deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@
"docs:check-claims": "deno run --allow-read tools/check-docs-truth.ts --check=claims",
"docs:check-recipe-parity": "deno run --allow-read tools/check-supabase-recipe-parity.ts",
"release:evidence:check": "deno run --allow-read --allow-run=git tools/check-docs-truth.ts --check=evidence",
"release:truth:check": "deno run --allow-read tools/check-release-truth.ts",
"release:state-machine:check": "deno run --allow-read --allow-run=git tools/check-release-state-machine.ts",
"docs:check-version-anchors": "deno run --allow-read tools/check-version-anchors.ts",
"docs:truth": "deno run --allow-read --allow-run=git tools/check-docs-truth.ts && deno run --allow-read tools/check-release-truth.ts && deno task docs:check-version-anchors && deno task docs:check-recipe-parity",
"docs:truth": "deno run --allow-read --allow-run=git tools/check-docs-truth.ts && deno task release:truth:check && deno task docs:check-version-anchors && deno task docs:check-recipe-parity",
"www:check-current-truth": "deno run --allow-read tools/check-docs-truth.ts --check=www",
"www:check-theme-tokens": "deno run --allow-read tools/check-www-theme-tokens.ts",
"www:check-artifact-truth": "deno run --allow-read tools/check-docs-truth.ts --check=www --artifacts",
Expand All @@ -76,7 +77,7 @@
"lint:markdown": "deno run -A npm:markdownlint-cli2@0.23.2 \"**/*.md\"",
"deno-api:check": "deno run --allow-read --allow-env tools/check-deno-api-free.ts",
"text-integrity:check": "deno run --allow-read --allow-run=git tools/check-docs-truth.ts --check=text",
"audit:citations:check": "deno run -A tools/check-audit-citations.ts",
"audit:citations:check": "deno run --allow-read --allow-run=git tools/check-audit-citations.ts",
"graph:check": "deno run --allow-read --allow-env tools/check-package-graph.ts",
"consumer:local": "deno run --allow-read --allow-write --allow-run --allow-env --allow-net tools/consumer-local.ts",
"consumer:packaged": "deno run --allow-read --allow-write --allow-run --allow-env --allow-net tools/consumer-packaged-starter.ts && deno run --allow-read --allow-write --allow-run --allow-env --allow-net tools/consumer-local.ts --packaged-import-map-check",
Expand Down
10 changes: 10 additions & 0 deletions docs/governance/PROJECT_WORKFLOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,13 @@ npm's default `latest` tag. `tools/verify-npm-release.ts` asserts
`deno task workflow:check` verifies that the workflow itself remains visible and
that the active version plan has the required shape. AutoFlow3 is the single
gate and evidence control plane for hooks and CI.

Gate ownership (#1230): `tools/autoflow/policy.ts` is the machine-readable gate
registry — each gate names exactly one deno task, and each task names its owning
script. Generic toolchain concerns (format, lint, type graph, Markdown
structure, secret content, workflow lint/security) are owned by the pinned OSS
tools themselves and wired as plain CI steps and git-hook calls (ADR-0144), not
as AutoFlow gates. Registry integrity — every gate resolving to an existing
task, and no two gates sharing one command — is asserted in
`tools/autoflow/__tests__/policy.test.ts`, so this document deliberately does
not duplicate the gate list.
71 changes: 71 additions & 0 deletions tools/autoflow/__tests__/policy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,19 @@ Deno.test('policy: release tier includes publish dry-run and nitro proofs', () =
assert(gates.includes('third-party-wc:smoke'));
});

Deno.test('policy: release-line truth is gated in ci and release tiers (#1230)', () => {
// check-release-truth.ts (release-state.json consistency + README/STATUS/
// ROADMAP registry anchors) must not depend on the local docs:truth
// composition alone — the CI-GATING rule requires a policy gate.
for (const tier of ['ci', 'release'] as const) {
const gates = selectGates(tier, ['docs/release/release-state.json']).map((gate) => gate.name);
assert(gates.includes('release:truth:check'), `release:truth:check missing from ${tier} tier`);
}
const gate = allRegisteredGates().find((candidate) => candidate.name === 'release:truth:check');
assert(gate, 'release:truth:check must be registered');
assertEquals(gate.command, ['deno', 'task', 'release:truth:check']);
});

Deno.test('policy: package artifacts gate packs before the packaged consumer runs', () => {
const gates = selectGates('ci', ['packages/element/src/index.ts']).map((gate) => gate.name);
assert(gates.indexOf('package-artifacts:check') < gates.indexOf('consumer:packaged'));
Expand Down Expand Up @@ -388,3 +401,61 @@ Deno.test('release: patch release plan omits publish and GitHub release outside
else Deno.env.set('CI', originalCi);
}
});

Deno.test('policy: every gate command resolves to an existing deno task (#1230)', async () => {
// policy.ts is the machine-readable gate registry; this assertion is the
// drift guard that keeps gate -> task -> owning script referentially intact.
const denoJson = JSON.parse(await Deno.readTextFile('deno.json')) as {
tasks: Record<string, string>;
};
const gates = allRegisteredGates();
assert(gates.length > 0);
for (const gate of gates) {
assertEquals(
gate.command.slice(0, 2),
['deno', 'task'],
`${gate.name} must invoke a deno task`,
);
const task = gate.command[2];
assert(task in denoJson.tasks, `${gate.name} references missing deno task "${task}"`);
}
});

Deno.test('policy: no two gates share the same command (#1230)', () => {
// One concern, one owner: two gates on the same command would double-run
// the same check and fork its ownership. Parameterized gates (same task,
// different args — e.g. per-browser smoke) are distinct concerns.
const seen = new Map<string, string>();
for (const gate of allRegisteredGates()) {
const command = gate.command.join(' ');
assertEquals(
seen.get(command),
undefined,
`gates "${seen.get(command)}" and "${gate.name}" both own command "${command}"`,
);
seen.set(command, gate.name);
}
});

function allRegisteredGates() {
const byName = new Map<string, ReturnType<typeof selectGates>[number]>();
for (const tier of ['dev', 'push', 'ci', 'release'] as const) {
// ci/release selection ignores triggers; a maximally-broad changed-path
// set additionally captures dev/push-only triggered gates.
const changedPaths = [
'packages/element/src/index.ts',
'docs/current/VERSION_PLAN.md',
'www/app/main.tsx',
'tools/autoflow/policy.ts',
'.github/workflows/autoflow-ci.yml',
'examples/supabase-cloudflare-starter/deno.json',
'deno.json',
'README.md',
'e2e/starter-smoke/setup.ts',
];
for (const gate of selectGates(tier, changedPaths)) {
byName.set(gate.name, gate);
}
}
return [...byName.values()];
}
22 changes: 21 additions & 1 deletion tools/autoflow/policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,26 @@ const GATES: readonly GateDefinition[] = [
/^deno\.json$/,
],
},
{
// #1230 (B2.8): check-release-truth.ts (release-state.json consistency +
// README/STATUS/ROADMAP registry anchors) previously ran only inside the
// local docs:truth composition — a release-truth check with no CI wiring
// violates the CI-GATING rule. Same command as the composition, matching
// the sibling release:evidence:check tier declaration.
name: 'release:truth:check',
command: ['deno', 'task', 'release:truth:check'],
tiers: ['ci', 'release'],
triggers: [
/^docs\/release\/release-state\.json$/,
/^docs\/(status|roadmap|current)\//,
/^README/,
/^examples\/supabase-cloudflare-starter\/deno\.json$/,
/^tools\/check-release-truth(?:\.test)?\.ts$/,
/^tools\/project-constants\.ts$/,
/^tools\/lib\/version\.ts$/,
/^deno\.json$/,
],
},
{
// Replays the durable autoflow3 release state machine recorded under
// docs/release/autoflow3/<tag>.json from git history and fails unless the
Expand Down Expand Up @@ -466,7 +486,7 @@ const GATES: readonly GateDefinition[] = [
triggers: [
/^www\/e2e\/visual-baselines\.spec\.ts$/,
/^www\/e2e\/visual-baselines\.spec\.ts-snapshots\//,
/^tools\/check-visual-baseline-duplicates\.ts$/,
/^tools\/check-visual-baseline-duplicates(?:\.test)?\.ts$/,
/^deno\.json$/,
],
},
Expand Down
7 changes: 4 additions & 3 deletions tools/check-audit-citations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
* `packages/adapter-vite/src/internal/ssg/ssg-render.ts`). Ambiguous
* basenames (e.g. `index.ts`) are flagged rather than guessed.
*
* Usage:
* deno run -A tools/check-audit-citations.ts [files...] [--sha=<commit>]
* deno run -A tools/check-audit-citations.ts --write # append a verification appendix
* Usage (least privilege — L12/#1230; the `audit:citations:check` task runs
* without --write, so it needs no write permission):
* deno run --allow-read --allow-run=git tools/check-audit-citations.ts [files...] [--sha=<commit>]
* deno run --allow-read --allow-write --allow-run=git tools/check-audit-citations.ts --write
*
* With no file arguments the tool scans docs/audit/ for reports archived under
* the YYYY-MM-DD-* naming convention. Archived reports are verified against
Expand Down
83 changes: 83 additions & 0 deletions tools/check-repo-hygiene.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { assertEquals } from '@std/assert';
import {
classifyTrackedBinary,
credentialFileFailure,
isActiveScanFile,
isAllowedRemovedPackageMention,
isAllowedTrackedIgnored,
isForbiddenRootTracked,
isForbiddenUntrackedResidue,
LARGE_BINARY_LIMIT_BYTES,
} from './check-repo-hygiene.ts';

// M15 (#1230): the hygiene gate's allow/deny classification is decision logic
// that can turn a failure into a success (a too-broad template carve-out or
// allowlist silently greens a tracked credential or binary). Pin it.

Deno.test('hygiene: tracked credential files fail, placeholder templates pass', () => {
// Real credentials are always failures.
assertEquals(typeof credentialFileFailure('.env'), 'string');
assertEquals(typeof credentialFileFailure('packages/app/.env'), 'string');
assertEquals(typeof credentialFileFailure('.env.production'), 'string');
assertEquals(typeof credentialFileFailure('certs/server.pem'), 'string');
assertEquals(typeof credentialFileFailure('.ssh/id_rsa'), 'string');
assertEquals(typeof credentialFileFailure('id_rsa.pub'), 'string');
// The template carve-out is exact: only .env.example/.sample/.template.
assertEquals(credentialFileFailure('.env.example'), undefined);
assertEquals(credentialFileFailure('examples/x/.env.sample'), undefined);
assertEquals(credentialFileFailure('.env.template'), undefined);
// Non-credentials pass.
assertEquals(credentialFileFailure('README.md'), undefined);
assertEquals(credentialFileFailure('tools/environment.ts'), undefined);
});

Deno.test('hygiene: large tracked binaries fail outside the allowed asset dirs', () => {
const over = LARGE_BINARY_LIMIT_BYTES + 1;
// Over-limit binaries are failures outside the allowlist...
assertEquals(typeof classifyTrackedBinary('packages/element/logo.png', over), 'string');
// ...and allowed in the intentional asset directories.
assertEquals(classifyTrackedBinary('www/design/mockups/home.png', over), undefined);
assertEquals(
classifyTrackedBinary('www/e2e/visual-baselines.spec.ts-snapshots/home.png', over),
undefined,
);
assertEquals(classifyTrackedBinary('examples/x/fixtures/banner.mp4', over), undefined);
assertEquals(classifyTrackedBinary('www/public/assets/dragon-hero.mp4', over), undefined);
// Under the limit or non-binary extensions are not this check's concern.
assertEquals(classifyTrackedBinary('packages/element/logo.png', 1024), undefined);
assertEquals(classifyTrackedBinary('packages/element/big.ts', over), undefined);
});

Deno.test('hygiene: root generated artifacts are tracked-file failures, nested ones are not', () => {
assertEquals(isForbiddenRootTracked('dist/server/index.js'), true);
assertEquals(isForbiddenRootTracked('playwright-report/index.html'), true);
assertEquals(isForbiddenRootTracked('debug.log'), true);
// Anchored at the repo root: package-level build output is gitignored, not
// this tripwire's concern.
assertEquals(isForbiddenRootTracked('packages/element/dist/mod.js'), false);
assertEquals(isForbiddenRootTracked('packages/element/src/mod.ts'), false);
});

Deno.test('hygiene: untracked workflow residue fails, other untracked files pass', () => {
assertEquals(isForbiddenUntrackedResidue('.github/workflows/debug.yml'), true);
assertEquals(isForbiddenUntrackedResidue('hub-submission.json'), true);
assertEquals(isForbiddenUntrackedResidue('notes.md'), false);
});

Deno.test('hygiene: only vendored license attributions may be tracked-and-ignored', () => {
assertEquals(isAllowedTrackedIgnored('vendor/jsr.io/@std/fs/LICENSE'), true);
assertEquals(isAllowedTrackedIgnored('vendor/jsr.io/std/LICENSE'), true);
assertEquals(isAllowedTrackedIgnored('vendor/jsr.io/@std/fs/mod.ts'), false);
});

Deno.test('hygiene: removed-package mention scan covers active roots only', () => {
assertEquals(isActiveScanFile('deno.json'), true);
assertEquals(isActiveScanFile('packages/element/src/mod.ts'), true);
assertEquals(isActiveScanFile('tools/check-repo-hygiene.ts'), true);
// docs/audit, docs/release and other historical trees are not scanned.
assertEquals(isActiveScanFile('docs/audit/2026-01-01-x.md'), false);
assertEquals(isActiveScanFile('packages/element/README.png'), false);
// The allowlist is exact-path, not substring.
assertEquals(isAllowedRemovedPackageMention('tools/check-repo-hygiene.ts'), true);
assertEquals(isAllowedRemovedPackageMention('tools/check-repo-hygiene-extra.ts'), false);
});
Loading
Loading