runward 0.18.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.
@@ -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.18.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",