runward 0.17.0 → 0.18.1

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.
package/README.md CHANGED
@@ -74,7 +74,9 @@ New here? Follow [your first mission in 15 minutes](docs/first-mission.md).
74
74
 
75
75
  Global flags: `--yes`, `--dry-run`, `--verbose`, `--no-color`. Exit codes: 0 success, 1 gaps/warnings, 2 missing prerequisite.
76
76
 
77
- The gate's exit code is a **port**: `runward/adapters/` ships inert sample wiring so the gate runs at each harness's natural moment. The git `pre-commit` and CI adapters are **agent-agnostic** — they gate whatever agent produced the code (Codex, Claude, Cursor, Copilot, Gemini); the Claude Code `Stop`-hook is one example of a per-harness turn-end hook, not a privileged one. You copy an adapter in; runward never installs or runs one for you.
77
+ The gate's exit code is a **port**: `runward/adapters/` ships inert sample wiring so the gate runs at each harness's natural moment.
78
+
79
+ **Install runward from where you already work.** Beyond the copy-in adapters, runward publishes distributable packagings — a **GitHub Action** (`uses: stranxik/runward@<sha>` as a required check, the hard governance gate at merge), a **Claude Code plugin** (`/plugin marketplace add stranxik/runward`), and siblings for Gemini CLI, Codex, Copilot, Cursor and Kiro — each honestly tiered by how hard its gate can block, plus an MCP descriptor for discovery only (an MCP tool is model-controlled — findable, never a gate). The full, honestly-tiered map is [`docs/distribution.md`](docs/distribution.md). The operator installs; none is privileged. The git `pre-commit` and CI adapters are **agent-agnostic** — they gate whatever agent produced the code (Codex, Claude, Cursor, Copilot, Gemini); the Claude Code `Stop`-hook is one example of a per-harness turn-end hook, not a privileged one. You copy an adapter in; runward never installs or runs one for you.
78
80
 
79
81
  `init` creates:
80
82
 
@@ -121,17 +123,19 @@ Phase 5 is transverse: it starts at day zero, not after the incident.
121
123
 
122
124
  ## What makes it different
123
125
 
124
- - **The gate you own deterministic and zero-LLM.** `runward check --strict` is a non-probabilistic, operator-owned exit-code gate: it cannot be jailbroken by injection (no model in the gate path) and reruns byte-for-byte. Every competitor gates on LLM generation plus human prose review; this one does not depend on an LLM to pass.
126
+ Four things, and a code-level competitive check (July 2026) confirmed no other scaffold pairs them: a **rule-level gate with verified, sealed evidence**, a **gated hand-over**, and a **published, deterministic OSCAL mapping**. Each exists somewhere in isolation; the combination is runward's ground. The rest of this section is those four and the invariants under them.
127
+
128
+ - **The gate you own — deterministic and zero-LLM.** `runward check --strict` is a non-probabilistic, operator-owned exit-code gate: it cannot be jailbroken by injection (no model in the gate path) and reruns byte-for-byte. Every competitor's *code* check is judged by the model; this one does not depend on an LLM to pass. (Two 2026 signals back the stance: FedRAMP RFC-0024 forbids GenAI-produced factual evidence, and the Delve affair showed why AI-produced audit evidence is not trusted.)
125
129
  - **The gate verifies the evidence, not just the row.** A pointer must resolve to real, non-empty content; a symbol or test name must actually be there; a rule can declare the shape its evidence must contain (`signature:` — the founding cited-not-applied incident is now caught deterministically); a stale pointer fails the gate; a sealed gate (`--freeze`) makes later erosion loud. Still bytes, never judgment — and never a model.
126
130
  - **runward gates itself.** This repository carries its own `runward/` mission, and CI requires `runward check --strict` green on runward's own code — including under network isolation. The discipline is demonstrated, not claimed.
127
- - **Audit-ready evidence, not compliance theatre.** Craft rules carry an OWASP ASI (Top 10 for Agentic Applications) mapping — a universal, region-agnostic security posture — so the conformance manifest reads as supporting evidence. Security-only by default; it maps to your regime when you need one — ISO/IEC 42001 (global), NIST AI RMF (US), or the EU AI Act art. 13 technical file (EU) — see [`docs/compliance/`](docs/compliance/). An input to that work — never a conformity assessment, never a claim to certify.
131
+ - **Audit-ready evidence, in a published format.** Craft rules carry an OWASP ASI (Top 10 for Agentic Applications) mapping — a universal, region-agnostic security posture — so the conformance manifest reads as supporting evidence and exports as a machine-readable OSCAL pack. The decision → ADR → manifest → OSCAL chain is a **versioned, citable mini-spec** ([`docs/spec/runward-oscal-mapping.md`](docs/spec/runward-oscal-mapping.md)) anyone can reproduce or critique — the reference implementation of gate-level agentic-delivery evidence, not a black box. Security-only by default; it maps to your regime when you need one — ISO/IEC 42001 (global), NIST AI RMF (US), or the EU AI Act art. 13 technical file (EU) — see [`docs/compliance/`](docs/compliance/). An input to that work — never a conformity assessment, never a claim to certify.
128
132
  - **Never a runtime.** Runward writes nothing into `.git/`, installs nothing, and runs nothing of yours — it frames, gates and hands off. Your runtime and model are swappable adapters behind a port; the thing you depend on is never Runward.
129
133
  - **Floor, not MVP deck.** The floor is the smallest *running* system that proves value on real traffic. A presentation is not a floor.
130
134
  - **Evolution on evidence.** Multi-agent, long-term memory, microservices, a bigger model: each has a sober default and an explicit trigger. No trigger, no complexity. Every switch is an ADR.
131
135
  - **Security by architecture, not detection.** Prompt injection is constrained structurally (lethal trifecta, 2-of-3 rule), not filtered heuristically.
132
- - **Craft rules, not vibes.** 58 engineering craft rules ship with the mission (memory scoring, tiered retrieval, event sourcing, request-id propagation, multi-provider fallback, cost routing, post-turn pipelines, prompt-injection defenses…) — your agent applies them, `runward check` will not invent them. Code examples follow the reference-stack default (a single language in the core — TypeScript by default, an adapter decision like any other: see the decision matrix). The patterns are the contract; the language is the adapter.
136
+ - **Craft rules, not vibes.** 64 engineering craft rules ship with the mission (memory scoring, tiered retrieval, event sourcing, request-id propagation, multi-provider fallback, cost routing, post-turn pipelines, prompt-injection defenses…) — your agent applies them, `runward check` will not invent them. Code examples follow the reference-stack default (a single language in the core — TypeScript by default, an adapter decision like any other: see the decision matrix). The patterns are the contract; the language is the adapter.
133
137
  - **One accountable operator, not a simulated team.** One human owns every gate while the agent executes the workflows — see [the operator role](docs/operator-role.md).
134
- - **Handover as a deliverable.** The mission ends when the receiving team is autonomous, with proof.
138
+ - **Hand-over gated on proof, not a folder.** The mission ends when the receiving team redoes a real task without you — and that succession is a gated deliverable (`handover.md`, four handover rules), verified like every other phase. No other scaffold carries the mission past tested code, let alone checks the succession.
135
139
 
136
140
  ## The reference floor
137
141
 
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { join, resolve } from "node:path";
3
3
  import { findMissionRoot } from "../lib/mission.js";
4
+ import { GATED_DELIVERABLES } from "../lib/conformance.js";
4
5
  import { readRuleSet, ruleSetDir, parseRule, ruleBody } from "../lib/rules.js";
5
6
  import { RULE_MIGRATIONS } from "../lib/rule-migrations.js";
6
7
  import { c, createHeader, section, status } from "../lib/styles.js";
@@ -27,7 +28,9 @@ export async function rulesCommand(opts) {
27
28
  return;
28
29
  }
29
30
  console.log(createHeader(`Runward v${VERSION} — rules`, `${rules.length} rule(s) · source: ${source}${opts.phase ? ` · phase: ${opts.phase}` : ""}`));
30
- const phases = ["architect", "topology", "floor", "govern"];
31
+ // The gated phases, from the single source (GATED_DELIVERABLES) — so a new gated phase (e.g.
32
+ // handover, ADR-0026) is never mislabelled "Unmapped/advisory" here by a stale hardcoded list.
33
+ const phases = GATED_DELIVERABLES.map((d) => d.phase);
31
34
  for (const ph of opts.phase ? [opts.phase] : [...phases, ""]) {
32
35
  const subset = ph === "" ? rules.filter((r) => r.phases.length === 0 || r.phases.every((p) => !phases.includes(p))) : rules.filter((r) => r.phases.includes(ph));
33
36
  if (subset.length === 0)
@@ -62,8 +62,14 @@ function clean(token) {
62
62
  * to catch every pathological regex. Signatures are simple token alternations in practice. See ADR-0020.
63
63
  */
64
64
  export function unsafeSignature(source) {
65
+ // Normalize character classes ([...], including [^()]) to a single token first: a quantifier INSIDE
66
+ // a class (e.g. `([^()]+)+`) otherwise hides the inner quantifier from the scan, because the scan's
67
+ // own `[^()]` stops at the `(` that lives literally inside the class. After normalization the class
68
+ // becomes `C`, so `([^()]+)+` reads as `(C+)+` and is caught.
69
+ const norm = source.replace(/\[(?:\\.|[^\]\\])*\]/g, "C");
65
70
  // group whose body holds a quantifier, immediately followed by another quantifier: (…+…)+ (…*…)* etc.
66
- return /\((?![?])[^()]*[+*}][^()]*\)[+*{]/.test(source);
71
+ const NESTED = /\((?![?])[^()]*[+*}][^()]*\)[+*{]/;
72
+ return NESTED.test(norm) || NESTED.test(source);
67
73
  }
68
74
  /** The same three resolution bases as the drift pass (ADR-0004). */
69
75
  export function resolutionBases(missionDir, deliverable) {
@@ -230,8 +236,16 @@ export function verifyEvidenceLock(missionDir) {
230
236
  }
231
237
  const files = lock.files ?? {};
232
238
  const violations = [];
239
+ const rootAbs = resolve(root);
233
240
  for (const [rel, hash] of Object.entries(files)) {
234
- const abs = join(root, rel);
241
+ // Contain the lock's keys to the project, exactly as the writer does: a forged lock with an
242
+ // absolute or `../`-escaping path must never make the verifier read/hash outside the project
243
+ // (an arbitrary-file read oracle, and a DoS via /dev/zero or a huge file). Same check as resolveFile.
244
+ const abs = resolve(rootAbs, rel);
245
+ if (isAbsolute(rel) || !(abs === rootAbs || abs.startsWith(rootAbs + sep))) {
246
+ violations.push({ rule: "(seal)", problem: `sealed path escapes the project: ${rel} — a lock entry must be a project-relative file. Re-seal with \`runward check --freeze\`` });
247
+ continue;
248
+ }
235
249
  if (!existsSync(abs) || !isRegularFile(abs)) {
236
250
  violations.push({ rule: "(seal)", problem: `sealed evidence missing: ${rel} — the file the gate crossed on is gone. Re-verify the pointer, then re-seal with \`runward check --freeze\`` });
237
251
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runward",
3
- "version": "0.17.0",
3
+ "version": "0.18.1",
4
4
  "description": "After the spec: ship and run. A delivery framework for agentic systems \u2014 floor first, evolution on evidence, governance from day zero, handover with proof.",
5
5
  "keywords": [
6
6
  "agentic",