runward 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +2 -2
  2. package/dist/cli.js +2 -0
  3. package/dist/commands/check.js +67 -2
  4. package/dist/commands/doctor.js +7 -1
  5. package/dist/lib/conformance.js +149 -0
  6. package/dist/lib/constants.js +4 -0
  7. package/dist/lib/hooks.js +26 -0
  8. package/dist/lib/paths.js +1 -1
  9. package/dist/lib/rule-migrations.js +7 -0
  10. package/package.json +1 -1
  11. package/templates/mission/architecture.md +8 -0
  12. package/templates/mission/floor.md +8 -0
  13. package/templates/mission/threat-model.md +8 -0
  14. package/templates/rules/async-job-guardrails.md +1 -0
  15. package/templates/rules/config-secrets-boundary.md +1 -0
  16. package/templates/rules/contracts-governance.md +1 -0
  17. package/templates/rules/eval-loop.md +1 -0
  18. package/templates/rules/frontier-deterministic-boundary.md +1 -0
  19. package/templates/rules/hexa-adapter-pattern.md +1 -0
  20. package/templates/rules/hexa-architecture.md +1 -0
  21. package/templates/rules/hexa-move-deterministic-out.md +1 -0
  22. package/templates/rules/hexa-typescript-native.md +1 -0
  23. package/templates/rules/process-adr-and-journal.md +1 -0
  24. package/templates/rules/provider-llm-auto-detection.md +1 -0
  25. package/templates/rules/provider-no-crash-missing-env.md +1 -0
  26. package/templates/rules/resilience-fail-open.md +1 -0
  27. package/templates/rules/resilience-multi-provider-fallback.md +1 -0
  28. package/templates/rules/resilience-retry-backoff.md +1 -0
  29. package/templates/rules/security-prompt-injection.md +1 -0
  30. package/templates/rules/state-event-sourcing.md +1 -0
  31. package/templates/rules/tools-scope-atomicity.md +1 -0
  32. package/templates/targets/AGENTS.md +1 -1
  33. package/templates/workflows/architect.md +3 -0
  34. package/templates/workflows/floor.md +3 -0
  35. package/templates/workflows/govern.md +3 -0
  36. package/templates/workflows/verify.md +33 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Runward
2
2
 
3
- [![License: MIT](https://img.shields.io/badge/License-MIT-0a0a0a?style=for-the-badge)](LICENSE) [![Doctrine: CC BY-ND 4.0](https://img.shields.io/badge/Doctrine-CC%20BY--ND%204.0-C9A45C?style=for-the-badge)](https://github.com/stranxik/designing-and-running-agentic-systems) [![Support via Ko-fi](https://img.shields.io/badge/%E2%98%95%20Support%20via%20Ko--fi-orange?style=for-the-badge)](https://ko-fi.com/stranxik)
3
+ [![npm](https://img.shields.io/npm/v/runward?style=for-the-badge&color=0a0a0a&label=npm)](https://www.npmjs.com/package/runward) [![License: MIT](https://img.shields.io/badge/License-MIT-0a0a0a?style=for-the-badge)](LICENSE) [![Doctrine: CC BY-ND 4.0](https://img.shields.io/badge/Doctrine-CC%20BY--ND%204.0-C9A45C?style=for-the-badge)](https://github.com/stranxik/designing-and-running-agentic-systems) [![Support via Ko-fi](https://img.shields.io/badge/%E2%98%95%20Support%20via%20Ko--fi-orange?style=for-the-badge)](https://ko-fi.com/stranxik)
4
4
 
5
5
  **From business need to a system that holds in production.**
6
6
 
@@ -8,7 +8,7 @@ Your agent builds fast. Runward frames what it builds — six gated phases your
8
8
 
9
9
  Runward is a delivery framework for agentic systems. It covers the entire mission, from framing to handover — it opens before any spec in greenfield, or picks up where the spec tools stop, and carries through to the run and the handover either way. Three ways in: day one of a new project (Frame is the first gate, before any spec exists), from an existing spec (Spec Kit, OpenSpec or in-house — it becomes the input of framing), or from an existing prototype (brownfield: characterize before touching anything). Then: floor first (the floor: the smallest *running* system that proves value on real traffic), evolution on evidence, governance from day zero, and a handover that makes your team autonomous.
10
10
 
11
- > Spec Kit, OpenSpec and BMAD stop at authoring: specs, plans, implementation. Runward pilots the whole mission — and picks up their output if you use them. What none of them *structures* is the run: governed memory, resilience, execution security, continuous evaluation, transmission.
11
+ > Spec Kit, OpenSpec and BMAD take you to tested, sometimes merged code. Runward pilots the whole mission — and picks up their output if you use them. What none of them *structures* is the run: governed memory, resilience, execution security, continuous evaluation, transmission.
12
12
 
13
13
  ## Why
14
14
 
package/dist/cli.js CHANGED
@@ -57,6 +57,8 @@ program
57
57
  .command("check")
58
58
  .description("can I cross the gate — gate audit, exit 1 on gaps (CI-friendly)")
59
59
  .option("-p, --path <path>", "project directory")
60
+ .option("--strict", "also verify the floor rule-conformance manifest (deterministic)")
61
+ .option("--hooks", "run operator hooks from runward/hooks.json around the audit (opt-in)")
60
62
  .action(checkCommand);
61
63
  program
62
64
  .command("status")
@@ -1,10 +1,16 @@
1
1
  import { join } from "node:path";
2
2
  import { analyze, findMissionRoot } from "../lib/mission.js";
3
+ import { conformance, driftReport } from "../lib/conformance.js";
4
+ import { runHooks } from "../lib/hooks.js";
3
5
  import { c, createHeader, section, status } from "../lib/styles.js";
4
6
  import { VERSION } from "../lib/paths.js";
5
7
  /**
6
8
  * Gate audit — the gap analysis: which deliverable, expected at which
7
9
  * phase, is present, started, or still a raw template.
10
+ * With --strict, also verifies the floor rule-conformance manifest (see
11
+ * docs/adr/ADR-0001): every CRITICAL/HIGH rule mapped to the floor phase must be
12
+ * accounted for. It checks the presence of a traced decision, never the quality
13
+ * of the implementation — that stays the operator's judgment at the gate.
8
14
  * Exit codes: 0 = current gate clean, 1 = gaps, 2 = no mission found.
9
15
  */
10
16
  export async function checkCommand(opts) {
@@ -16,6 +22,15 @@ export async function checkCommand(opts) {
16
22
  const mission = join(root, "runward");
17
23
  const report = analyze(mission);
18
24
  console.log(createHeader(`Runward v${VERSION} — gate audit`, root));
25
+ let hookFailed = 0;
26
+ if (opts.hooks) {
27
+ const before = runHooks(mission, "before", root);
28
+ if (before.ran > 0) {
29
+ console.log(section("Hooks · before"));
30
+ console.log(" " + (before.failed.length ? status.error(`${before.failed.length}/${before.ran} failed`) : status.success(`${before.ran} ok`)));
31
+ hookFailed += before.failed.length;
32
+ }
33
+ }
19
34
  const glyph = {
20
35
  "filled": c.success("✓"),
21
36
  "in-progress": c.warning("◐"),
@@ -37,14 +52,64 @@ export async function checkCommand(opts) {
37
52
  gaps++;
38
53
  }
39
54
  }
55
+ let strictGaps = 0;
56
+ if (opts.strict) {
57
+ const CONFORMANCE = [
58
+ { phase: "architect", deliverable: "architecture.md", label: "Architect" },
59
+ { phase: "floor", deliverable: "floor.md", label: "Floor" },
60
+ { phase: "govern", deliverable: "governance/threat-model.md", label: "Govern" },
61
+ ];
62
+ console.log(section("Rule conformance (--strict)"));
63
+ let checked = 0;
64
+ const drift = [];
65
+ for (const { phase, deliverable, label } of CONFORMANCE) {
66
+ for (const d of driftReport(mission, deliverable))
67
+ drift.push(`${label} · ${d.rule} — ${d.problem}`);
68
+ const { expected, violations } = conformance(mission, phase, deliverable);
69
+ if (expected.length === 0)
70
+ continue;
71
+ checked++;
72
+ if (violations.length === 0) {
73
+ console.log(` ${status.success(`${label}: ${expected.length} rule(s) accounted for`)}`);
74
+ }
75
+ else {
76
+ for (const v of violations)
77
+ console.log(` ${c.error("✗")} ${c.darkGray(label + " · ")}${c.white(v.rule)}${c.darkGray(" — " + v.problem)}`);
78
+ strictGaps += violations.length;
79
+ }
80
+ }
81
+ if (checked === 0)
82
+ console.log(" " + c.darkGray("no CRITICAL/HIGH rules mapped to a build phase"));
83
+ if (drift.length > 0) {
84
+ console.log(section("Drift (advisory)"));
85
+ for (const d of drift)
86
+ console.log(` ${c.warning("◑")} ${c.white(d)}`);
87
+ console.log(" " + c.darkGray("advisory — an applied pointer no longer resolves; verify it. Does not fail the gate."));
88
+ }
89
+ }
90
+ if (opts.hooks) {
91
+ const after = runHooks(mission, "after", root);
92
+ if (after.ran > 0) {
93
+ console.log(section("Hooks · after"));
94
+ console.log(" " + (after.failed.length ? status.error(`${after.failed.length}/${after.ran} failed`) : status.success(`${after.ran} ok`)));
95
+ hookFailed += after.failed.length;
96
+ }
97
+ }
40
98
  console.log(section("Summary"));
41
99
  console.log(` ${c.primaryBold("Current gate")} ${c.white(report.currentPhase)}`);
42
100
  console.log(` ${c.primaryBold("ADRs")} ${c.white(String(report.adrCount))}${report.adrCount === 0 ? c.warning(" — no structural decision locked yet") : ""}`);
43
- if (gaps === 0) {
101
+ if (gaps === 0 && strictGaps === 0 && hookFailed === 0) {
44
102
  console.log("\n" + status.success("All expected deliverables are filled. Cross gates on evidence, not on paperwork."));
45
103
  }
46
104
  else {
47
- console.log("\n" + status.warning(`${gaps} deliverable(s) not filled. The gate rule: no phase closes without its artifact.`));
105
+ const parts = [];
106
+ if (gaps)
107
+ parts.push(`${gaps} deliverable(s) not filled`);
108
+ if (strictGaps)
109
+ parts.push(`${strictGaps} floor rule-conformance gap(s)`);
110
+ if (hookFailed)
111
+ parts.push(`${hookFailed} hook(s) failed`);
112
+ console.log("\n" + status.warning(`${parts.join(" · ")}. No phase closes without its artifact — and, under --strict, without its CRITICAL/HIGH rules accounted for.`));
48
113
  process.exitCode = 1;
49
114
  }
50
115
  }
@@ -2,7 +2,8 @@ import { execFileSync } from "node:child_process";
2
2
  import { existsSync, readdirSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { TEMPLATES, MISSION_LAYOUT, VERSION, WORKFLOWS } from "../lib/paths.js";
5
- import { EXPECTED_RULES } from "../lib/constants.js";
5
+ import { EXPECTED_RULES, EXPECTED_MAPPED } from "../lib/constants.js";
6
+ import { expectedRules } from "../lib/conformance.js";
6
7
  import { findMissionRoot } from "../lib/mission.js";
7
8
  import { createHeader, section, status } from "../lib/styles.js";
8
9
  /**
@@ -34,6 +35,11 @@ export async function doctorCommand() {
34
35
  const rulesDir = join(TEMPLATES, "rules");
35
36
  const ruleCount = existsSync(rulesDir) ? readdirSync(rulesDir).filter((f) => f.endsWith(".md")).length : 0;
36
37
  ruleCount === EXPECTED_RULES ? ok(`${ruleCount} craft rules`) : fail(`craft rules mismatch: ${ruleCount}/${EXPECTED_RULES}`);
38
+ // Routed-count floor (ADR-0002): the phases: mapping must not be stripped below its pinned minimum.
39
+ const belowFloor = Object.entries(EXPECTED_MAPPED).filter(([phase, floor]) => expectedRules(TEMPLATES, phase).length < floor);
40
+ belowFloor.length === 0
41
+ ? ok(`rule mapping floors met (${Object.entries(EXPECTED_MAPPED).map(([p, f]) => `${p}≥${f}`).join(", ")})`)
42
+ : fail(`rule mapping below floor: ${belowFloor.map(([p, f]) => `${p} ${expectedRules(TEMPLATES, p).length}/${f}`).join(", ")}`);
37
43
  console.log(section("Current directory"));
38
44
  const root = findMissionRoot(process.cwd());
39
45
  if (!root) {
@@ -0,0 +1,149 @@
1
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
2
+ import { join, dirname } from "node:path";
3
+ import { TEMPLATES } from "./paths.js";
4
+ import { EXPECTED_MAPPED } from "./constants.js";
5
+ import { RULE_MIGRATIONS } from "./rule-migrations.js";
6
+ const FRONTMATTER = /^---\n([\s\S]*?)\n---/;
7
+ const VALID_STATUS = new Set(["applied", "deviated", "n/a"]);
8
+ /** An n/a reason must be more than a placeholder: real length, not a bracketed template token. */
9
+ function trivialReason(s) {
10
+ const t = s.trim();
11
+ return t.length < 8 || /^\[.*\]$/.test(t);
12
+ }
13
+ function parseRuleMeta(content) {
14
+ const fm = content.match(FRONTMATTER)?.[1] ?? "";
15
+ const impact = (fm.match(/^impact:\s*(.+)$/m)?.[1] ?? "").trim();
16
+ const phasesRaw = fm.match(/^phases:\s*\[(.*)\]/m)?.[1] ?? "";
17
+ const phases = phasesRaw.split(",").map((s) => s.trim()).filter(Boolean);
18
+ return { impact, phases };
19
+ }
20
+ /** CRITICAL/HIGH rules mapped to a phase — the set that must be accounted for.
21
+ * The mapping is a property of the rule definitions: read the mission's own
22
+ * `runward/rules/` when present, else fall back to the package rules (the
23
+ * authoritative source — covers missions predating rules-in-mission). */
24
+ export function expectedRules(missionDir, phaseId) {
25
+ const missionRules = join(missionDir, "rules");
26
+ const dir = existsSync(missionRules) ? missionRules : join(TEMPLATES, "rules");
27
+ if (!existsSync(dir))
28
+ return [];
29
+ return readdirSync(dir)
30
+ .filter((f) => f.endsWith(".md"))
31
+ .filter((f) => {
32
+ const { impact, phases } = parseRuleMeta(readFileSync(join(dir, f), "utf8"));
33
+ return (impact === "CRITICAL" || impact === "HIGH") && phases.includes(phaseId);
34
+ })
35
+ .map((f) => f.replace(/\.md$/, ""))
36
+ .sort();
37
+ }
38
+ /** Every rule slug in the set (mission's own, else the package) — the universe a manifest row must belong to. */
39
+ export function allRules(missionDir) {
40
+ const missionRules = join(missionDir, "rules");
41
+ const dir = existsSync(missionRules) ? missionRules : join(TEMPLATES, "rules");
42
+ if (!existsSync(dir))
43
+ return [];
44
+ return readdirSync(dir).filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, ""));
45
+ }
46
+ /** Parse the "## Rule conformance" markdown table from a deliverable. */
47
+ export function parseManifest(content) {
48
+ const lines = content.split("\n");
49
+ const start = lines.findIndex((l) => /^##\s+Rule conformance/i.test(l));
50
+ if (start === -1)
51
+ return [];
52
+ const rows = [];
53
+ for (let i = start + 1; i < lines.length; i++) {
54
+ const line = lines[i];
55
+ if (/^##\s/.test(line))
56
+ break; // next section
57
+ if (!line.trim().startsWith("|"))
58
+ continue;
59
+ const cols = line.split("|").slice(1, -1).map((c) => c.replace(/`/g, "").trim());
60
+ if (cols.length < 3)
61
+ continue;
62
+ const [rule, status, evidence] = cols;
63
+ if (/^rule$/i.test(rule) || /^:?-+:?$/.test(rule))
64
+ continue; // header / separator
65
+ rows.push({ rule, status: status.toLowerCase(), evidence });
66
+ }
67
+ return rows;
68
+ }
69
+ function adrExists(missionDir, evidence) {
70
+ const id = evidence.match(/ADR-\d+/i)?.[0].toUpperCase();
71
+ if (!id)
72
+ return false;
73
+ const dir = join(missionDir, "adr");
74
+ return existsSync(dir) && readdirSync(dir).some((f) => f.toUpperCase().startsWith(id));
75
+ }
76
+ // A path token: a file with a known code/doc extension (excludes version numbers like v1.0, "§2").
77
+ const PATH_TOKEN = /[\w./-]+\.(?:ts|tsx|js|jsx|mjs|cjs|py|md|json|ya?ml|toml|go|rs|java|rb|php|sql|sh|css|scss|html|txt)\b/g;
78
+ /** Advisory drift (ADR-0004): applied pointers whose file path no longer resolves. Existence only. */
79
+ export function driftReport(missionDir, deliverable) {
80
+ const path = join(missionDir, deliverable);
81
+ if (!existsSync(path))
82
+ return [];
83
+ const bases = [dirname(missionDir), missionDir, dirname(path)];
84
+ const out = [];
85
+ for (const row of parseManifest(readFileSync(path, "utf8"))) {
86
+ if (row.status !== "applied")
87
+ continue;
88
+ const tokens = row.evidence.match(PATH_TOKEN) ?? [];
89
+ if (tokens.length === 0)
90
+ continue; // pure prose reference — the operator's judgment
91
+ const resolves = tokens.some((t) => bases.some((b) => existsSync(join(b, t))));
92
+ if (!resolves)
93
+ out.push({ rule: row.rule, problem: `applied pointer does not resolve (drift?): ${row.evidence} — update the pointer or remove the row` });
94
+ }
95
+ return out;
96
+ }
97
+ /** Verify a build phase's conformance manifest against its expected rule set. */
98
+ export function conformance(missionDir, phaseId, deliverable) {
99
+ const expected = expectedRules(missionDir, phaseId);
100
+ const violations = [];
101
+ // Non-vacuity (ADR-0002): the mapping cannot be stripped below its pinned floor.
102
+ const floor = EXPECTED_MAPPED[phaseId];
103
+ if (floor !== undefined && expected.length < floor) {
104
+ violations.push({ rule: "(mapping)", problem: `only ${expected.length} CRITICAL/HIGH rules mapped to '${phaseId}', floor is ${floor} — the mapping may have been stripped; restore the phases: [...] frontmatter on this phase's rules` });
105
+ }
106
+ const path = join(missionDir, deliverable);
107
+ if (!existsSync(path)) {
108
+ return { expected, violations: expected.map((rule) => ({ rule, problem: `${deliverable} missing` })) };
109
+ }
110
+ const rows = parseManifest(readFileSync(path, "utf8"));
111
+ // Form-lint (ADR-0003): well-formedness before the semantic check. Skip template placeholder tokens.
112
+ const known = new Set(allRules(missionDir));
113
+ const counts = new Map();
114
+ for (const r of rows) {
115
+ if (/^\[.*\]$/.test(r.rule))
116
+ continue;
117
+ counts.set(r.rule, (counts.get(r.rule) ?? 0) + 1);
118
+ }
119
+ for (const [rule, n] of counts) {
120
+ if (!known.has(rule)) {
121
+ const m = RULE_MIGRATIONS[rule];
122
+ const hint = m
123
+ ? (m.to ? ` — renamed to '${m.to}' in ${m.since} (${m.reason})` : ` — removed in ${m.since} (${m.reason})`)
124
+ : " (typo? not in runward/rules/)";
125
+ violations.push({ rule, problem: `unknown rule${hint}` });
126
+ }
127
+ if (n > 1)
128
+ violations.push({ rule, problem: `listed ${n} times in the manifest — keep a single row per rule` });
129
+ }
130
+ const byRule = new Map(rows.map((r) => [r.rule, r]));
131
+ for (const rule of expected) {
132
+ const row = byRule.get(rule);
133
+ if (!row) {
134
+ violations.push({ rule, problem: "not accounted for in the Rule conformance manifest — add a row: applied with a file:line/test, deviated with an ADR, or n/a with a reason" });
135
+ continue;
136
+ }
137
+ if (!VALID_STATUS.has(row.status)) {
138
+ violations.push({ rule, problem: `invalid status "${row.status}" (use applied | deviated | n/a)` });
139
+ continue;
140
+ }
141
+ if (row.status === "applied" && !row.evidence)
142
+ violations.push({ rule, problem: "applied without an evidence pointer — put a file:line or a test in the Evidence column" });
143
+ if (row.status === "deviated" && !adrExists(missionDir, row.evidence))
144
+ violations.push({ rule, problem: "deviated but no matching ADR in runward/adr/ — reference an ADR that exists there" });
145
+ if (row.status === "n/a" && trivialReason(row.evidence))
146
+ violations.push({ rule, problem: "n/a with an empty or placeholder reason — give a real one-line reason why it does not apply here" });
147
+ }
148
+ return { expected, violations };
149
+ }
@@ -1,2 +1,6 @@
1
1
  /** Total number of craft rules shipped under templates/rules/. */
2
2
  export const EXPECTED_RULES = 51;
3
+ /** Routed-count floor: minimum CRITICAL/HIGH rules mapped to each build phase (ADR-0002).
4
+ * Lowering a floor is a deliberate, tracked edit — the `phases:` mapping cannot be silently
5
+ * stripped to make `check --strict` pass vacuously. */
6
+ export const EXPECTED_MAPPED = { architect: 5, floor: 10, govern: 7 };
@@ -0,0 +1,26 @@
1
+ import { execSync } from "node:child_process";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ export function runHooks(missionDir, phase, cwd) {
5
+ const result = { ran: 0, failed: [] };
6
+ const path = join(missionDir, "hooks.json");
7
+ if (!existsSync(path))
8
+ return result;
9
+ let cfg;
10
+ try {
11
+ cfg = JSON.parse(readFileSync(path, "utf8"));
12
+ }
13
+ catch {
14
+ return result;
15
+ }
16
+ for (const cmd of cfg[phase] ?? []) {
17
+ result.ran++;
18
+ try {
19
+ execSync(cmd, { cwd, stdio: "inherit" });
20
+ }
21
+ catch {
22
+ result.failed.push(cmd);
23
+ }
24
+ }
25
+ return result;
26
+ }
package/dist/lib/paths.js CHANGED
@@ -24,5 +24,5 @@ export const MISSION_LAYOUT = {
24
24
  };
25
25
  export const WORKFLOWS = [
26
26
  "method", "frame", "architect", "floor", "iterate",
27
- "govern", "handover", "brownfield", "review", "decision-loop",
27
+ "govern", "handover", "brownfield", "review", "decision-loop", "verify",
28
28
  ];
@@ -0,0 +1,7 @@
1
+ export const RULE_MIGRATIONS = {
2
+ "hexa-llm-boundary-principle": {
3
+ to: "hexa-move-deterministic-out",
4
+ reason: "the name is reserved for the founding inversion",
5
+ since: "v0.7.0",
6
+ },
7
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runward",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "After the spec: ship and run. A delivery framework for agentic systems — floor first, evolution on evidence, governance from day zero, handover with proof.",
5
5
  "keywords": ["agentic", "ai-agents", "spec-driven", "delivery", "governance", "llm", "architecture"],
6
6
  "author": "Thibault Souris",
@@ -37,6 +37,14 @@
37
37
  | Tool registry + middleware chain | [single transversal surface, thin channel] | [—] |
38
38
  | One core language, thin model SDK | [no heavy chain framework] | [mature library or proven performance need → sidecar] |
39
39
 
40
+ ## Rule conformance
41
+
42
+ > Account for every CRITICAL/HIGH craft rule mapped to the architect phase (`runward/rules/`, frontmatter `phases: [architect]`). `applied` needs a pointer; `deviated` needs an ADR; `n/a` needs a reason. `runward check --strict` verifies this table is complete — it checks a traced decision, not the quality of your architecture.
43
+
44
+ | Rule | Status | Evidence |
45
+ |---|---|---|
46
+ | [rule-slug] | applied \| deviated \| n/a | [pointer, ADR-id, or reason] |
47
+
40
48
  ## 5. What stays open
41
49
 
42
50
  [Explicitly undecided: language(s), framework, model provider, hosting. Each is an adapter decision, taken later behind the contracts above, justified by a local technical reason.]
@@ -17,6 +17,14 @@
17
17
  | Deterministic guardrails | [shipped] | [classification, validation, mutation access control] |
18
18
  | Baseline observability + cost ceiling | [shipped] | [request ID propagated; per-run ceiling: [value]] |
19
19
 
20
+ ## Rule conformance
21
+
22
+ > Account for every CRITICAL/HIGH craft rule mapped to the floor phase (`runward/rules/`, frontmatter `phases: [floor]`). Status: `applied` needs an evidence pointer (a `file:line` or a test); `deviated` needs an ADR reference; `n/a` needs a one-line reason. `runward check --strict` verifies this table is complete and well-formed — it does not judge your implementation; you do, at the gate.
23
+
24
+ | Rule | Status | Evidence |
25
+ |---|---|---|
26
+ | [rule-slug] | applied \| deviated \| n/a | [file:line, a test, ADR-id, or a reason] |
27
+
20
28
  ## 2. Proof against the success criterion
21
29
 
22
30
  [The measurement, on real traffic or a representative sample — never on cases picked to impress.]
@@ -48,6 +48,14 @@
48
48
 
49
49
  **Reminder**: the approval summary is itself an attack surface. It must be deterministic and faithful to the real arguments, or an injected action slips through in the batch. Group low-urgency requests into a prioritized queue with summaries, so approvers are not trained to rubber-stamp.
50
50
 
51
+ ## Rule conformance
52
+
53
+ > Account for every CRITICAL/HIGH craft rule mapped to the govern phase (`runward/rules/`, frontmatter `phases: [govern]`). `applied` needs a pointer; `deviated` needs an ADR; `n/a` needs a reason. `runward check --strict` verifies this table is complete — it checks a traced decision, not the quality of your governance.
54
+
55
+ | Rule | Status | Evidence |
56
+ |---|---|---|
57
+ | [rule-slug] | applied \| deviated \| n/a | [pointer, ADR-id, or reason] |
58
+
51
59
  ## References
52
60
 
53
61
  - [Related hardening ADR, observability schema, evaluation rubric.]
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: Background Job Guardrails (Retry, Idempotency, Bounded Concurrency, Job Observability)
3
3
  impact: HIGH
4
+ phases: [govern]
4
5
  impactDescription: The four non-negotiables of any background job — bounded retry per step, idempotency under concurrency, capped and partitioned concurrency, and queue lag plus failure rate as first-class metrics
5
6
  tags: [async, jobs, idempotency, concurrency, retry, observability]
6
7
  ---
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: Secrets at the Network Boundary, Never in the Model
3
3
  impact: CRITICAL
4
+ phases: [floor, govern]
4
5
  impactDescription: Makes secret disclosure structurally impossible by keeping the real key out of the model and the domain
5
6
  tags: [security, secrets, configuration, boundary]
6
7
  ---
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: Contract Governance (Versioned, Additive, Expand-then-Contract)
3
3
  impact: CRITICAL
4
+ phases: [architect]
4
5
  impactDescription: Lets the system evolve without breaking consumers, by governing the contract at the boundary rather than the implementation behind it
5
6
  tags: [architecture, contracts, ports, versioning, compatibility]
6
7
  ---
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: Evaluation Loop (Test + Evaluate, Hold-out, Anchored Judge)
3
3
  impact: CRITICAL
4
+ phases: [govern]
4
5
  impactDescription: Gives a non-deterministic system a trustworthy way to detect behavioural regressions without letting it optimise its own grader
5
6
  tags: [evaluation, testing, llm, quality, governance]
6
7
  ---
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: Deterministic Boundary of the Model
3
3
  impact: CRITICAL
4
+ phases: [floor]
4
5
  impactDescription: Keeps every fact, figure and decision that can be checked out of the model, so the system is verifiable and cannot hallucinate load-bearing values
5
6
  tags: [architecture, llm, frontier, grounding, safety, determinism]
6
7
  ---
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: New Feature = New Adapter
3
3
  impact: HIGH
4
+ phases: [architect, floor]
4
5
  impactDescription: Keeps core business logic clean and external integrations isolated
5
6
  tags: [architecture, hexagonal, adapters, integration]
6
7
  ---
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: Hexagonal Architecture Structure
3
3
  impact: HIGH
4
+ phases: [architect, floor]
4
5
  impactDescription: Enables testability, maintainability, and clean dependency management
5
6
  tags: [architecture, hexagonal, structure, testing]
6
7
  ---
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: Move the Deterministic out of the Model
3
3
  impact: CRITICAL
4
+ phases: [floor]
4
5
  impactDescription: Reduces LLM costs and latency by moving deterministic logic out of LLM calls
5
6
  tags: [architecture, llm, cost-optimization, performance]
6
7
  ---
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: A Thin Model Abstraction You Own (Not a Heavy Chain Framework)
3
3
  impact: HIGH
4
+ phases: [architect]
4
5
  impactDescription: Reduces token overhead, improves debuggability, removes volatile dependencies by keeping a light abstraction you control instead of a heavy framework
5
6
  tags: [architecture, typescript, llm, frameworks]
6
7
  ---
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: ADR Discipline and Working Journal
3
3
  impact: HIGH
4
+ phases: [architect]
4
5
  impactDescription: Makes structural decisions traceable and reversible-by-record, so the system can be picked up by anyone without re-litigating settled choices
5
6
  tags: [process, adr, journal, traceability, governance]
6
7
  ---
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: Model Provider as an Adapter (Detected, Never Hardcoded)
3
3
  impact: CRITICAL
4
+ phases: [floor]
4
5
  impactDescription: Keeps the model behind a port so the provider can change by configuration without touching the core
5
6
  tags: [provider, llm, embeddings, ports, configuration, portability]
6
7
  ---
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: Graceful Degradation of Optional Services
3
3
  impact: HIGH
4
+ phases: [floor]
4
5
  impactDescription: Lets the app start and run wherever it is deployed, enabling features by available config rather than crashing on a missing variable
5
6
  tags: [provider, configuration, resilience, deployment]
6
7
  ---
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: Fail-Open for the Non-Critical, Fail-Closed for the Sensitive
3
3
  impact: CRITICAL
4
+ phases: [govern]
4
5
  impactDescription: Degrades gracefully where safe and denies safely where it matters, instead of applying one failure policy everywhere
5
6
  tags: [resilience, error-handling, availability, safety]
6
7
  ---
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: Multi-Provider Fallback
3
3
  impact: HIGH
4
+ phases: [govern]
4
5
  impactDescription: Ensures LLM availability by falling back to alternative providers
5
6
  tags: [resilience, llm, providers, availability]
6
7
  ---
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: LLM Retry with Exponential Backoff
3
3
  impact: HIGH
4
+ phases: [govern]
4
5
  impactDescription: Handles transient LLM failures gracefully without overwhelming the API
5
6
  tags: [resilience, llm, retry, error-handling]
6
7
  ---
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: Untrusted Input, Prompt Injection and the Lethal Trifecta
3
3
  impact: CRITICAL
4
+ phases: [floor, govern]
4
5
  impactDescription: Treats prompt injection as a first-rank, structural threat constrained by architecture rather than detected by heuristics
5
6
  tags: [security, llm, injection, trifecta, untrusted-input]
6
7
  ---
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: State as an Immutable Journal (Event Sourcing, Replay, Provenance)
3
3
  impact: HIGH
4
+ phases: [floor]
4
5
  impactDescription: Makes the agent a stateless reducer over an external, replayable journal, so truth, audit and recovery are structural rather than bolted on
5
6
  tags: [state, event-sourcing, replay, provenance, memory]
6
7
  ---
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: Tool Scope and Atomicity
3
3
  impact: HIGH
4
+ phases: [floor]
4
5
  impactDescription: Reduces token usage and improves LLM tool selection accuracy
5
6
  tags: [tools, llm, architecture, cost-optimization]
6
7
  ---
@@ -12,7 +12,7 @@ This project is delivered with the Runward method: floor first, evolution on evi
12
12
 
13
13
  ## How to work
14
14
 
15
- - Apply the craft rules in `runward/rules/` while building: they cover memory scoring, tiered retrieval, event sourcing, request-id propagation, resilience, cost routing, secrets and prompt-injection defenses. When a rule and a habit conflict, the rule wins; deviating from a rule requires an ADR.
15
+ - Apply the craft rules in `runward/rules/` while building and confront them at the point of action, not from memory. Each rule declares where it applies (`phases:`); a build phase surfaces its CRITICAL/HIGH rules to open and account for in the deliverable's `Rule conformance` manifest: `applied` with a `file:line` or test, `deviated` with an ADR, `n/a` with a reason. When a rule and a habit conflict, the rule wins; deviating requires an ADR. `runward check --strict` verifies the manifest is complete — it checks that a decision was traced, never the quality of the code; you judge that at the gate.
16
16
  - Consult `runward/decision-matrix.md` before adding any capability: 22 arbitrations, each with a sober default and an explicit trigger. No trigger, no change.
17
17
  - Before any structural decision, run `runward/workflows/decision-loop.md`: verify in the real code, check the sourced state of the art, challenge the source, take a durable position, lock it in an ADR — only then edit.
18
18
  - One ADR per structural decision, in `runward/adr/`, with a dated re-evaluation trigger. Use the template `runward/adr/ADR-0000-template.md`.
@@ -30,6 +30,8 @@ Use this workflow once framing is decided and structure must follow: "how do we
30
30
 
31
31
  **Lock structuring choices in ADRs.** Starting topology, core language, legacy integration strategy, bounded-context boundaries: each goes through `decision-loop` — reality-check against reference implementations, sourced state of the art, challenge, durable position, written lock. A decision that is not locked does not enter the architecture note.
32
32
 
33
+ **Confront the architect craft rules at the point of deciding.** Open the CRITICAL/HIGH rules mapped to the architect phase (`runward/rules/`, `phases: [architect]`): contract governance, the hexagonal architecture and adapter pattern, the single core language, the ADR-and-journal discipline. Do not work from their names — read them. Account for each in the `Rule conformance` manifest of the architecture note: `applied` with a pointer, `deviated` with an ADR, or `n/a` with a reason. `runward check --strict` verifies that manifest.
34
+
33
35
  **Write the architecture note.** Short, readable, decided: the ports, the default topology and its triggers, what stays open (language, provider), and the target named without being built. A few pages, not a detailed design dossier.
34
36
 
35
37
  ## Definition of Done
@@ -37,6 +39,7 @@ Use this workflow once framing is decided and structure must follow: "how do we
37
39
  - Architecture note produced, boundaries first, stack open.
38
40
  - Every port named with contract and initial version; integration protocol stated.
39
41
  - One ADR per structuring choice, locked before the note mentions it.
42
+ - Every architect CRITICAL/HIGH rule accounted for in the conformance manifest (`runward check --strict`).
40
43
  - Note reviewed via `review` before circulation.
41
44
  - Deliverable form matched: the presented architecture note carries an expected delivery form (a readable PDF or HTML); ADRs and contract specs stay as repository markdown.
42
45
 
@@ -21,6 +21,8 @@ Use this workflow once the architecture is fixed and building must start: "what
21
21
 
22
22
  **Start from the reference floor, not a blank page.** The starting point is the reference floor (`floor-ts/` in the Runward repository): a clonable hexagonal scaffold that already carries the pure domain, the ports, the adapters, and the middleware chain, with a principle-to-code table mapping each principle to where it lives in the code. Clone it, implement the project's concrete adapters behind the ports fixed in `architect`, and leave intact the structure that keeps the domain testable without the model. Do not reinvent the skeleton; populate it.
23
23
 
24
+ **Confront the floor's craft rules at the point of building.** Before writing each piece, open the CRITICAL/HIGH rules mapped to the floor phase — `runward/rules/`, frontmatter `phases: [floor]`: the deterministic frontier guard, the model port and its keyless fallback, provider auto-detection, secrets boundary, prompt-injection defense, the hexagonal architecture and adapter pattern, the event-sourced journal, tool-scope atomicity. Do not work from their names — read them. Account for each in the `Rule conformance` manifest of the floor note: `applied` with a `file:line` or test, `deviated` with an ADR, or `n/a` with a reason. `runward check --strict` verifies that manifest is complete and well-formed (it checks that a decision was traced, never the quality of the code — you judge that at the gate).
25
+
24
26
  **Build exactly these six pieces, and nothing more.**
25
27
 
26
28
  1. **An entry point matched to actual use.** A minimal interface when a human operates the system (submit, review, approve, decide); an API or tool protocol when another system drives it. It is a primary adapter — plain, replaceable, never touching the domain, and never a demo showcase.
@@ -48,6 +50,7 @@ Lock any structuring decision met during the build through `decision-loop`. Writ
48
50
  - Value measured against the observable success criterion.
49
51
  - A complete trajectory replays from a single request ID.
50
52
  - Every deferral named with its trigger.
53
+ - Every floor CRITICAL/HIGH rule accounted for in the conformance manifest (`runward check --strict` clean).
51
54
  - Floor note produced and reviewed.
52
55
 
53
56
  ## Anti-patterns
@@ -35,9 +35,12 @@ The chain carries transversal concerns only — no orchestration, no business lo
35
35
 
36
36
  **Test and evaluate — both.** Build the test pyramid: unit tests without network (pure domain, mocked model adapter); consumer-driven contract tests that catch drift between schema and real data; integration through the injection container with mock adapters; behavioral evaluations at the top. Then run the continuous evaluation loop: sample the trace stream off the hot path; score hybrid — deterministic checks wherever a guarantee exists, an anchored judge model (pinned version or replayed anchor set) only for the irreducibly behavioral, abstention first. The loop is valid only under a **hold-out the optimizer never sees**; self-tuning stays inside a pre-approved, audited envelope or goes through human validation — never autonomous self-rewriting. The hard floor — safety, security, authorization, audit — stays deterministic; never assemble it from soft judgments. Promote a new model via shadow deployment: same port, real traffic, silent; measure divergence with the same evaluation; roll out in stages with instant rollback.
37
37
 
38
+ **Confront the govern craft rules.** Open the CRITICAL/HIGH rules mapped to the govern phase (`runward/rules/`, `phases: [govern]`): the evaluation loop, prompt-injection defense, the secrets boundary, fail-open/fail-closed resilience, provider fallback, retry-with-backoff, background-job guardrails. Account for each in the `Rule conformance` manifest of the threat model: `applied` with a pointer, `deviated` with an ADR, or `n/a` with a reason. `runward check --strict` verifies that manifest.
39
+
38
40
  ## Definition of Done
39
41
 
40
42
  - All transversal concerns pass through the single middleware chain; a trajectory replays from one request ID.
43
+ - Every govern CRITICAL/HIGH rule accounted for in the conformance manifest (`runward check --strict`).
41
44
  - Cost ceilings enforced with stop-and-synthesize behavior.
42
45
  - Threat model written; two-of-three trifecta rule enforced while untrusted content is in the context window.
43
46
  - Test pyramid in place; evaluation loop running with anchored judge and hold-out.
@@ -0,0 +1,33 @@
1
+ # Verify — Advisory Cite-vs-Apply Review (above the gate, never in it)
2
+
3
+ ## When to use
4
+
5
+ Use this **after** `runward check --strict` is green, and before you cross the gate as the operator. The deterministic gate proves a decision was *traced* for every CRITICAL/HIGH rule (applied with a pointer, deviated with an ADR, n/a with a reason). It cannot prove the code an `applied` row points at **actually applies** the rule rather than merely citing it — that is a semantic judgment. This workflow makes that judgment, adversarially, to inform yours.
6
+
7
+ It is **advisory**. It produces findings, never a verdict that gates. `runward check --strict` stays the load-bearing decision; you cross it. See [ADR-0007].
8
+
9
+ ## Inputs
10
+
11
+ - The `Rule conformance` manifests (`floor.md`, `architecture.md`, `governance/threat-model.md`).
12
+ - The code each `applied` row points at.
13
+
14
+ ## Procedure
15
+
16
+ **Challenge each `applied` claim, adversarially.** For every `applied` row, open its evidence (the `file:line` or test) and default to skepticism: *does this code actually apply the rule it names, or does it only cite it?* Judge through distinct lenses — correctness, does-it-reproduce, security-relevance — and, where you can, run this pass on a **different model** than the one that built the floor, so it does not agree with itself.
17
+
18
+ **Return a finding per row**: `confirmed` (the code applies the rule), `cited-not-applied` (the row claims applied but the code does not carry the rule), or `uncertain` (needs a human look). Deduplicate overlapping findings; group them by severity.
19
+
20
+ **Hand the findings to the operator.** They are input to the operator's gate decision, not a gate of their own. A `cited-not-applied` finding is a prompt to fix the code (or downgrade the row to `deviated`/`n/a`) and re-run `check --strict` — never a silent override of the deterministic gate.
21
+
22
+ ## Definition of Done
23
+
24
+ - Every `applied` row reviewed, with a confirmed / cited-not-applied / uncertain finding.
25
+ - Findings handed to the operator; no exit code produced, no gate blocked.
26
+ - Any `cited-not-applied` finding routed back to the code and re-checked deterministically.
27
+
28
+ ## Anti-patterns
29
+
30
+ - Treating a clean `verify` pass as "the gate passed" — it is advisory; the deterministic `check --strict` is the gate.
31
+ - Wiring `verify` into CI as a blocking step, or into an exit code — it never blocks.
32
+ - Running `verify` **instead of** `check --strict` — it sits above the deterministic gate, it does not replace it.
33
+ - Running it on the same model that wrote the code and trusting the agreement — use a different lens, ideally a different model.