@mikenguyen69/harness 0.1.0-beta.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.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +43 -0
  3. package/dist/build-identity.json +1 -0
  4. package/dist/cancellation.js +138 -0
  5. package/dist/cli.js +47 -0
  6. package/dist/dispatch.js +70 -0
  7. package/dist/help.js +47 -0
  8. package/dist/internal/agent/adapter/conformance.js +53 -0
  9. package/dist/internal/agent/adapter/hook.js +113 -0
  10. package/dist/internal/agent/adapter/install.js +30 -0
  11. package/dist/internal/agent/adapter/scope.js +55 -0
  12. package/dist/internal/agent/adapter/session.js +63 -0
  13. package/dist/internal/agent/adapter/settings.js +51 -0
  14. package/dist/internal/agent/cli.js +79 -0
  15. package/dist/internal/agent/evals/index.js +111 -0
  16. package/dist/internal/agent/index.js +15 -0
  17. package/dist/internal/agent/ledger-read.js +27 -0
  18. package/dist/internal/agent/routing/cli.js +104 -0
  19. package/dist/internal/agent/routing/index.js +123 -0
  20. package/dist/internal/agent/telemetry/index.js +97 -0
  21. package/dist/internal/orchestration/activity/index.js +111 -0
  22. package/dist/internal/orchestration/analyze/index.js +237 -0
  23. package/dist/internal/orchestration/cli.js +475 -0
  24. package/dist/internal/orchestration/config/index.js +66 -0
  25. package/dist/internal/orchestration/doctor/index.js +202 -0
  26. package/dist/internal/orchestration/forge/index.js +121 -0
  27. package/dist/internal/orchestration/git/index.js +181 -0
  28. package/dist/internal/orchestration/harness/cli.js +378 -0
  29. package/dist/internal/orchestration/harness/index.js +186 -0
  30. package/dist/internal/orchestration/harness/shim.js +182 -0
  31. package/dist/internal/orchestration/index.js +29 -0
  32. package/dist/internal/orchestration/loop/index.js +1107 -0
  33. package/dist/internal/orchestration/prompts/index.js +199 -0
  34. package/dist/internal/orchestration/report/index.js +125 -0
  35. package/dist/internal/orchestration/routes/index.js +85 -0
  36. package/dist/internal/orchestration/run/index.js +221 -0
  37. package/dist/internal/orchestration/runner/index.js +90 -0
  38. package/dist/internal/orchestration/runners/claude/index.js +246 -0
  39. package/dist/internal/orchestration/runners/claude/sdk.js +198 -0
  40. package/dist/internal/orchestration/runners/claude/wiring.js +98 -0
  41. package/dist/internal/orchestration/runners/codex/exec.js +114 -0
  42. package/dist/internal/orchestration/runners/codex/index.js +72 -0
  43. package/dist/internal/orchestration/runners/conformance/index.js +169 -0
  44. package/dist/internal/orchestration/runners/cursor/exec.js +531 -0
  45. package/dist/internal/orchestration/runners/cursor/index.js +184 -0
  46. package/dist/internal/orchestration/runners/stub/index.js +168 -0
  47. package/dist/internal/orchestration/runners/telemetry.js +36 -0
  48. package/dist/internal/orchestration/runstate/index.js +213 -0
  49. package/dist/internal/orchestration/runstate/publish.js +115 -0
  50. package/dist/internal/orchestration/worktree/index.js +89 -0
  51. package/dist/internal/system/adapters/index.js +19 -0
  52. package/dist/internal/system/agentops/index.js +16 -0
  53. package/dist/internal/system/agentops/scope.js +67 -0
  54. package/dist/internal/system/assets.js +45 -0
  55. package/dist/internal/system/board/cli.js +56 -0
  56. package/dist/internal/system/board/index.js +276 -0
  57. package/dist/internal/system/board/live.js +277 -0
  58. package/dist/internal/system/calibration/cli.js +45 -0
  59. package/dist/internal/system/calibration/index.js +141 -0
  60. package/dist/internal/system/cli.js +174 -0
  61. package/dist/internal/system/corpus/replay.js +79 -0
  62. package/dist/internal/system/digest/cli.js +23 -0
  63. package/dist/internal/system/digest/index.js +145 -0
  64. package/dist/internal/system/doors/classify.js +94 -0
  65. package/dist/internal/system/doors/cli.js +184 -0
  66. package/dist/internal/system/doors/index.js +249 -0
  67. package/dist/internal/system/doors/metrics.js +47 -0
  68. package/dist/internal/system/escapes/cli.js +45 -0
  69. package/dist/internal/system/escapes/index.js +81 -0
  70. package/dist/internal/system/explore/cli.js +77 -0
  71. package/dist/internal/system/explore/index.js +91 -0
  72. package/dist/internal/system/gates/cli.js +43 -0
  73. package/dist/internal/system/gates/detectors.js +55 -0
  74. package/dist/internal/system/gates/pre.js +51 -0
  75. package/dist/internal/system/hub/cli.js +51 -0
  76. package/dist/internal/system/hub/index.js +146 -0
  77. package/dist/internal/system/init/index.js +83 -0
  78. package/dist/internal/system/ledger/cli.js +54 -0
  79. package/dist/internal/system/ledger/index.js +176 -0
  80. package/dist/internal/system/manifest/index.js +207 -0
  81. package/dist/internal/system/provenance/index.js +13 -0
  82. package/dist/internal/system/report/index.js +15 -0
  83. package/dist/internal/system/runner/index.js +74 -0
  84. package/dist/internal/system/runstatus/cli.js +86 -0
  85. package/dist/internal/system/runstatus/index.js +168 -0
  86. package/dist/internal/system/sequencer/briefing.js +75 -0
  87. package/dist/internal/system/sequencer/cli.js +170 -0
  88. package/dist/internal/system/sequencer/index.js +156 -0
  89. package/dist/internal/system/spec/cli.js +110 -0
  90. package/dist/internal/system/spec/index.js +212 -0
  91. package/dist/internal/system/spec/materialize.js +111 -0
  92. package/dist/internal/system/spec/openspec-parse.js +83 -0
  93. package/dist/internal/system/spec/project.js +208 -0
  94. package/dist/internal/system/substrate/index.js +14 -0
  95. package/dist/internal/system/system/index.js +159 -0
  96. package/dist/internal/system/units/index.js +87 -0
  97. package/dist/internal/system/verify/cli.js +103 -0
  98. package/dist/internal/system/verify/declined-reasons.js +61 -0
  99. package/dist/internal/system/verify/index.js +188 -0
  100. package/dist/internal/system/verify/signing.js +79 -0
  101. package/dist/paths.js +50 -0
  102. package/dist/routes.js +93 -0
  103. package/dist/version.js +30 -0
  104. package/dist/win32-process-group.js +360 -0
  105. package/package.json +41 -0
  106. package/profiles/python/bindings.toml +59 -0
  107. package/profiles/terraform/bindings.toml +66 -0
  108. package/profiles/typescript/bindings.toml +64 -0
  109. package/profiles/typescript/smoke/README.md +8 -0
  110. package/schema/baseline.schema.json +26 -0
  111. package/schema/decision-classes.yaml +45 -0
  112. package/schema/envelope.schema.json +186 -0
  113. package/schema/ledger-event.schema.json +235 -0
  114. package/schema/manifest.schema.json +119 -0
  115. package/schema/routes.schema.json +55 -0
  116. package/schema/run-status.schema.json +162 -0
  117. package/schema/session-event.schema.json +74 -0
  118. package/schema/spec.schema.json +114 -0
  119. package/schema/system.schema.json +142 -0
  120. package/templates/ci/verify.yml +23 -0
  121. package/templates/target-kit/README.md +78 -0
  122. package/templates/target-kit/component.toml +20 -0
  123. package/templates/target-kit/orchestration.json +9 -0
  124. package/templates/target-kit/profiles/target/bindings.toml +6 -0
  125. package/templates/target-kit/routes.toml +5 -0
  126. package/templates/target-kit/specs/first-unit.yaml +11 -0
  127. package/templates/target-kit/system.toml +7 -0
@@ -0,0 +1,176 @@
1
+ /**
2
+ * ledger — the record plane (schema/ledger-event.schema.json, solution design §5).
3
+ *
4
+ * One append-only, hash-chained event log per system, stored as JSONL segments
5
+ * under `.harness/ledger/`. Every event carries `prev_hash` (the previous
6
+ * event's `hash`) and `hash` (SHA-256 over this event's canonical JSON with the
7
+ * `hash` key removed). Any edit, reorder, deletion or truncation-in-the-middle
8
+ * is detectable by `verifyChain`.
9
+ *
10
+ * Invariants:
11
+ * - seq is 0-based, strictly +1 per event, no gaps
12
+ * - the genesis event (seq 0) has prev_hash = 64 zeros
13
+ * - hashing is deterministic (canonical JSON: sorted keys, no whitespace,
14
+ * undefined dropped) so a chain re-verifies byte-for-byte
15
+ */
16
+ import { createHash } from "node:crypto";
17
+ import { existsSync, mkdirSync, readdirSync, readFileSync, appendFileSync, openSync, closeSync, unlinkSync, } from "node:fs";
18
+ import { join } from "node:path";
19
+ export const ZERO_HASH = "0".repeat(64);
20
+ const SEGMENT_RE = /^\d{6}\.jsonl$/;
21
+ const FIRST_SEGMENT = "000000.jsonl";
22
+ export class LedgerConflict extends Error {
23
+ expected;
24
+ actual;
25
+ name = "LedgerConflict";
26
+ constructor(expected, actual) {
27
+ super(`ledger moved: expected tail ${expected.slice(0, 12)}…, found ${actual.slice(0, 12)}…`);
28
+ this.expected = expected;
29
+ this.actual = actual;
30
+ }
31
+ }
32
+ /** Deterministic serialization: sorted keys, no whitespace, undefined dropped. */
33
+ export function canonicalJSON(value) {
34
+ if (value === null || typeof value !== "object")
35
+ return JSON.stringify(value) ?? "null";
36
+ if (Array.isArray(value))
37
+ return "[" + value.map(canonicalJSON).join(",") + "]";
38
+ const obj = value;
39
+ const keys = Object.keys(obj)
40
+ .filter((k) => obj[k] !== undefined)
41
+ .sort();
42
+ return ("{" +
43
+ keys.map((k) => JSON.stringify(k) + ":" + canonicalJSON(obj[k])).join(",") +
44
+ "}");
45
+ }
46
+ export function sha256hex(input) {
47
+ return createHash("sha256").update(input, "utf8").digest("hex");
48
+ }
49
+ /** The hash an event must carry: over its canonical form with `hash` removed. */
50
+ export function hashEvent(event) {
51
+ return sha256hex(canonicalJSON(event));
52
+ }
53
+ function segmentFiles(dir) {
54
+ if (!existsSync(dir))
55
+ return [];
56
+ return readdirSync(dir)
57
+ .filter((n) => SEGMENT_RE.test(n))
58
+ .sort();
59
+ }
60
+ /** Every event, in chain order, across all segments. */
61
+ export function read(dir) {
62
+ const out = [];
63
+ for (const seg of segmentFiles(dir)) {
64
+ const text = readFileSync(join(dir, seg), "utf8");
65
+ for (const line of text.split("\n")) {
66
+ if (line.trim() === "")
67
+ continue;
68
+ out.push(JSON.parse(line));
69
+ }
70
+ }
71
+ return out;
72
+ }
73
+ /** The chain tail: seq and hash of the last event, or genesis defaults. */
74
+ function tail(dir) {
75
+ const events = read(dir);
76
+ const last = events.at(-1);
77
+ return last
78
+ ? { seq: last.seq, hash: last.hash }
79
+ : { seq: -1, hash: ZERO_HASH };
80
+ }
81
+ /**
82
+ * Cross-process mutex over one ledger dir: an exclusive-create lock file. All
83
+ * writers serialize through this, so the tail read + CAS check + append is
84
+ * atomic against other processes on the same filesystem.
85
+ */
86
+ function withLock(dir, fn) {
87
+ const lockPath = join(dir, ".lock");
88
+ const deadline = Date.now() + 5000;
89
+ let fd;
90
+ for (;;) {
91
+ try {
92
+ fd = openSync(lockPath, "wx");
93
+ break;
94
+ }
95
+ catch (e) {
96
+ if (e.code !== "EEXIST")
97
+ throw e;
98
+ if (Date.now() > deadline)
99
+ throw new Error(`ledger lock at ${lockPath} held too long`, { cause: e });
100
+ // brief sync spin — appends are sub-millisecond
101
+ const until = Date.now() + 3 + Math.floor(Math.random() * 5);
102
+ while (Date.now() < until) {
103
+ /* spin */
104
+ }
105
+ }
106
+ }
107
+ try {
108
+ return fn();
109
+ }
110
+ finally {
111
+ closeSync(fd);
112
+ try {
113
+ unlinkSync(lockPath);
114
+ }
115
+ catch {
116
+ /* already gone */
117
+ }
118
+ }
119
+ }
120
+ /** Append one event; returns the full event as written. */
121
+ export function append(dir, ev, opts = {}) {
122
+ mkdirSync(dir, { recursive: true });
123
+ return withLock(dir, () => {
124
+ const prev = tail(dir);
125
+ if (opts.expectPrevHash !== undefined &&
126
+ opts.expectPrevHash !== prev.hash) {
127
+ throw new LedgerConflict(opts.expectPrevHash, prev.hash);
128
+ }
129
+ const unhashed = {
130
+ v: 1,
131
+ seq: prev.seq + 1,
132
+ ts: ev.ts ?? new Date().toISOString(),
133
+ system: ev.system,
134
+ actor: ev.actor,
135
+ type: ev.type,
136
+ prev_hash: prev.hash,
137
+ ...(ev.sig !== undefined ? { sig: ev.sig } : {}),
138
+ body: ev.body,
139
+ };
140
+ const event = { ...unhashed, hash: hashEvent(unhashed) };
141
+ const segs = segmentFiles(dir);
142
+ const target = segs.at(-1) ?? FIRST_SEGMENT;
143
+ appendFileSync(join(dir, target), JSON.stringify(event) + "\n", "utf8");
144
+ return event;
145
+ });
146
+ }
147
+ /** Recompute every hash and link; report the first break by seq. */
148
+ export function verifyChain(dir) {
149
+ const events = read(dir);
150
+ let expectedPrev = ZERO_HASH;
151
+ for (let i = 0; i < events.length; i++) {
152
+ const e = events[i];
153
+ if (e.seq !== i)
154
+ return {
155
+ ok: false,
156
+ seq: e.seq,
157
+ reason: `seq ${e.seq} out of order (expected ${i})`,
158
+ };
159
+ if (e.prev_hash !== expectedPrev)
160
+ return {
161
+ ok: false,
162
+ seq: e.seq,
163
+ reason: `prev_hash mismatch at seq ${e.seq}`,
164
+ };
165
+ const { hash, ...unhashed } = e;
166
+ if (hashEvent(unhashed) !== hash)
167
+ return {
168
+ ok: false,
169
+ seq: e.seq,
170
+ reason: `content hash mismatch at seq ${e.seq} (tampered)`,
171
+ };
172
+ expectedPrev = hash;
173
+ }
174
+ return { ok: true, length: events.length };
175
+ }
176
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,207 @@
1
+ /**
2
+ * manifest — loads and resolves component.toml against schema/manifest.schema.json
3
+ * (Contract A). (ADR 0003: the file is component.toml, not harness.toml.)
4
+ *
5
+ * Invariants owned here:
6
+ * - every required vocabulary gate is bound or explicitly declined with a
7
+ * reason; absence is a config error ("all-skip ≠ pass" made structural)
8
+ * - a gate name outside the closed vocabulary is a config error, not a skip
9
+ * - `external: true` gates never run in `verify` context (hermetic rule)
10
+ * - severity and cost are data — the maturity ramp never forks the tooling
11
+ * - the resolved bindings hash into Contract B's binding_hash
12
+ */
13
+ import { createHash } from "node:crypto";
14
+ import { readFileSync } from "node:fs";
15
+ import { parse as parseToml } from "smol-toml";
16
+ export function isDeclination(e) {
17
+ return "declined" in e;
18
+ }
19
+ export class ManifestError extends Error {
20
+ name = "ManifestError";
21
+ }
22
+ /**
23
+ * The abstract vocabulary (ADR 0004). The standard owns these names and the
24
+ * semantics of "pass". CLOSED: an unknown gate name is a config error.
25
+ * `sec:*` is a namespace — any `sec:<tool>` gate (sec:deps, sec:secrets, …) is
26
+ * valid; see GATE_NAMESPACES.
27
+ */
28
+ export const GATE_VOCABULARY = [
29
+ "spec",
30
+ "fmt",
31
+ "lint",
32
+ "types",
33
+ "test",
34
+ "arch",
35
+ "contract",
36
+ "size",
37
+ "gen:fresh",
38
+ ];
39
+ /** Namespace prefixes: a gate named `<prefix><tool>` is in-vocabulary. */
40
+ export const GATE_NAMESPACES = ["sec:"];
41
+ /** Namespaces that must have ≥1 concrete gate present (security can't be silently absent). */
42
+ const REQUIRED_NAMESPACES = ["sec:"];
43
+ /** True iff `name` is a valid gate name under the closed vocabulary. */
44
+ export function isVocabularyGate(name) {
45
+ return (GATE_VOCABULARY.includes(name) ||
46
+ GATE_NAMESPACES.some((p) => name.startsWith(p) && name.length > p.length));
47
+ }
48
+ // ---------------------------------------------------------------------------
49
+ // parsing
50
+ // ---------------------------------------------------------------------------
51
+ function asEntry(name, v) {
52
+ if (typeof v !== "object" || v === null) {
53
+ throw new ManifestError(`gate "${name}": expected a binding or a declination table`);
54
+ }
55
+ const o = v;
56
+ if ("declined" in o) {
57
+ if (typeof o.declined !== "string" || o.declined.trim().length < 10) {
58
+ throw new ManifestError(`gate "${name}": declination reason must be ≥10 chars`);
59
+ }
60
+ return { declined: o.declined };
61
+ }
62
+ if (typeof o.run !== "string" || o.run === "") {
63
+ throw new ManifestError(`gate "${name}": binding needs a non-empty "run"`);
64
+ }
65
+ const severity = o.severity;
66
+ if (!["advisory", "changed-lines", "blocking"].includes(severity)) {
67
+ throw new ManifestError(`gate "${name}": severity "${String(o.severity)}" is not advisory|changed-lines|blocking`);
68
+ }
69
+ const cost = o.cost;
70
+ if (!["cheap", "expensive"].includes(cost)) {
71
+ throw new ManifestError(`gate "${name}": cost "${String(o.cost)}" is not cheap|expensive`);
72
+ }
73
+ const entry = { run: o.run, severity, cost };
74
+ if (typeof o.external === "boolean")
75
+ entry.external = o.external;
76
+ if (typeof o.timeout_seconds === "number")
77
+ entry.timeoutSeconds = o.timeout_seconds;
78
+ if (typeof o.snapshot_max_age_days === "number")
79
+ entry.snapshotMaxAgeDays = o.snapshot_max_age_days;
80
+ return entry;
81
+ }
82
+ function parseGates(raw) {
83
+ const gates = {};
84
+ const src = (raw ?? {});
85
+ for (const [name, v] of Object.entries(src)) {
86
+ if (!isVocabularyGate(name)) {
87
+ throw new ManifestError(`gate "${name}" is outside the closed vocabulary (${GATE_VOCABULARY.join(", ")}, sec:*)`);
88
+ }
89
+ gates[name] = asEntry(name, v);
90
+ }
91
+ return gates;
92
+ }
93
+ /** Parse and structurally validate a component.toml. */
94
+ export function loadManifest(path) {
95
+ const raw = parseToml(readFileSync(path, "utf8"));
96
+ const h = (raw.harness ?? {});
97
+ if (typeof h.policy_version !== "string" || typeof h.profile !== "string") {
98
+ throw new ManifestError(`${path}: [harness] needs policy_version and profile`);
99
+ }
100
+ const mode = h.mode ?? "standard";
101
+ if (mode !== "standard" && mode !== "ratchet") {
102
+ throw new ManifestError(`${path}: [harness].mode must be standard|ratchet`);
103
+ }
104
+ const manifest = {
105
+ harness: { policyVersion: h.policy_version, profile: h.profile, mode },
106
+ gates: parseGates(raw.gates),
107
+ };
108
+ if (mode === "ratchet") {
109
+ const r = (raw.ratchet ?? {});
110
+ const baselinePath = r.baseline_path ?? ".harness/baseline.json";
111
+ manifest.ratchet = { baselinePath };
112
+ }
113
+ const sub = raw.substrate;
114
+ if (sub && typeof sub.enforced_from === "string") {
115
+ manifest.substrate = { enforcedFrom: sub.enforced_from };
116
+ }
117
+ return manifest;
118
+ }
119
+ /** Parse a profile's bindings.toml. */
120
+ export function loadProfile(path) {
121
+ const raw = parseToml(readFileSync(path, "utf8"));
122
+ const h = (raw.harness ?? {});
123
+ return {
124
+ profileVersion: h.profile_version ?? "0.0.0",
125
+ gates: parseGates(raw.gates),
126
+ };
127
+ }
128
+ // ---------------------------------------------------------------------------
129
+ // resolution
130
+ // ---------------------------------------------------------------------------
131
+ function bindingHash(bound) {
132
+ const canon = [...bound]
133
+ .sort((a, b) => a.name.localeCompare(b.name))
134
+ .map((g) => ({
135
+ name: g.name,
136
+ run: g.run,
137
+ severity: g.severity,
138
+ cost: g.cost,
139
+ external: g.external,
140
+ }));
141
+ return createHash("sha256")
142
+ .update(JSON.stringify(canon), "utf8")
143
+ .digest("hex");
144
+ }
145
+ /**
146
+ * Resolve a component manifest against its profile: the component's entry for a
147
+ * gate wins; a gate the component omits falls back to the profile's entry.
148
+ *
149
+ * @param opts.context "verify" (default) rejects `external: true` gates — verify
150
+ * stays hermetic; those gates run only in the deploy phase.
151
+ */
152
+ export function resolveManifest(manifest, profile, opts = {}) {
153
+ const context = opts.context ?? "verify";
154
+ const names = new Set([
155
+ ...Object.keys(profile.gates),
156
+ ...Object.keys(manifest.gates),
157
+ ]);
158
+ const bound = [];
159
+ const declined = [];
160
+ for (const name of names) {
161
+ const entry = manifest.gates[name] ?? profile.gates[name];
162
+ if (isDeclination(entry)) {
163
+ declined.push({ name, reason: entry.declined });
164
+ continue;
165
+ }
166
+ if (context === "verify" && entry.external) {
167
+ throw new ManifestError(`gate "${name}" is external:true — it runs only in the deploy phase, never in verify`);
168
+ }
169
+ bound.push({
170
+ name,
171
+ run: entry.run,
172
+ severity: entry.severity,
173
+ cost: entry.cost,
174
+ external: entry.external ?? false,
175
+ ...(entry.timeoutSeconds !== undefined
176
+ ? { timeoutSeconds: entry.timeoutSeconds }
177
+ : {}),
178
+ ...(entry.snapshotMaxAgeDays !== undefined
179
+ ? { snapshotMaxAgeDays: entry.snapshotMaxAgeDays }
180
+ : {}),
181
+ });
182
+ }
183
+ // every base vocabulary gate must be present (bound or declined)
184
+ const covered = new Set([
185
+ ...bound.map((g) => g.name),
186
+ ...declined.map((d) => d.name),
187
+ ]);
188
+ for (const req of GATE_VOCABULARY) {
189
+ if (!covered.has(req)) {
190
+ throw new ManifestError(`gate "${req}" is neither bound nor declined — absence is a config error`);
191
+ }
192
+ }
193
+ for (const ns of REQUIRED_NAMESPACES) {
194
+ if (![...covered].some((n) => n.startsWith(ns))) {
195
+ throw new ManifestError(`no "${ns}*" gate is bound or declined — security cannot be silently absent`);
196
+ }
197
+ }
198
+ return {
199
+ policyVersion: manifest.harness.policyVersion,
200
+ profile: manifest.harness.profile,
201
+ mode: manifest.harness.mode,
202
+ bound,
203
+ declined,
204
+ bindingHash: bindingHash(bound),
205
+ };
206
+ }
207
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * provenance — writes and verifies attestations for a merge: commit → spec →
3
+ * model → approval chain. Shaped after the SLSA Source Track (v1.2) — the
4
+ * track that covers authoring/review before the build — with in-toto link
5
+ * metadata as the wire format.
6
+ *
7
+ * Honesty caveat carried from Contract B: until signing lands, agent identity
8
+ * is self-reported. The attested flag makes that visible instead of pretended.
9
+ */
10
+ export function writeAttestation(_a, _outDir) {
11
+ throw new Error("not implemented (skeleton)");
12
+ }
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ * report — merges per-gate SARIF output into one file and wraps it in the
3
+ * Contract B envelope (schema/envelope.schema.json).
4
+ *
5
+ * The envelope is the provenance record and the ONLY evidence `work done`
6
+ * accepts — the agent's claim is never evidence; the artifact is.
7
+ *
8
+ * Kept in sync with the extended schema (review pass): `componentId`,
9
+ * `featureId`, `sessionId`, `artifacts`, `scopeAttested`, `signature`, and
10
+ * per-gate `preFailures`.
11
+ */
12
+ export function emitEnvelope(_envelope, _outDir) {
13
+ throw new Error("not implemented (skeleton)");
14
+ }
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,74 @@
1
+ /**
2
+ * runner — executes resolved gates: cheap first, retries recorded per gate,
3
+ * run at the component root with a scrubbed environment.
4
+ *
5
+ * "No network during verify" is enforced structurally, not by a sandbox: a
6
+ * gate that needs the network must declare `external: true`, and
7
+ * `resolveManifest({ context: "verify" })` refuses those. OS-level egress
8
+ * blocking is a substrate concern (later). What this module does: strip proxy
9
+ * vars, set HARNESS_HERMETIC=1, and run each `run` string at `cwd`.
10
+ *
11
+ * Deliberately thin: process orchestration is commodity. The differentiated
12
+ * product is manifest semantics (src/manifest), the envelope (src/verify +
13
+ * src/report), the generators (src/adapters), and the corpus.
14
+ */
15
+ import { spawnSync } from "node:child_process";
16
+ const STRIP_ENV = [
17
+ "HTTP_PROXY",
18
+ "HTTPS_PROXY",
19
+ "http_proxy",
20
+ "https_proxy",
21
+ "ALL_PROXY",
22
+ "all_proxy",
23
+ "NO_PROXY",
24
+ "no_proxy",
25
+ "npm_config_proxy",
26
+ "npm_config_https_proxy",
27
+ ];
28
+ function hermeticEnv() {
29
+ const env = { ...process.env, HARNESS_HERMETIC: "1" };
30
+ for (const k of STRIP_ENV)
31
+ delete env[k];
32
+ return env;
33
+ }
34
+ function runOnce(cmd, cwd, cap) {
35
+ const r = spawnSync(cmd, {
36
+ cwd,
37
+ shell: true,
38
+ encoding: "utf8",
39
+ env: hermeticEnv(),
40
+ windowsHide: true,
41
+ });
42
+ const output = ((r.stdout ?? "") + (r.stderr ?? "")).slice(-cap);
43
+ return { code: r.status, output };
44
+ }
45
+ /** Run every bound gate, cheap-first. Ordering only — no fail-fast short-circuit yet. */
46
+ export function runGates(bound, opts) {
47
+ const cap = opts.outputCap ?? 4000;
48
+ const maxRetries = opts.retries ?? 0;
49
+ const ordered = [...bound].sort((a, b) => {
50
+ const rank = (c) => (c === "cheap" ? 0 : 1);
51
+ return rank(a.cost) - rank(b.cost) || a.name.localeCompare(b.name);
52
+ });
53
+ const results = [];
54
+ for (const gate of ordered) {
55
+ const start = Date.now();
56
+ let attempt = 0;
57
+ let last = runOnce(gate.run, opts.cwd, cap);
58
+ while (last.code !== 0 && attempt < maxRetries) {
59
+ attempt++;
60
+ last = runOnce(gate.run, opts.cwd, cap);
61
+ }
62
+ results.push({
63
+ name: gate.name,
64
+ outcome: last.code === 0 ? "pass" : "fail",
65
+ severity: gate.severity,
66
+ durationMs: Date.now() - start,
67
+ retries: attempt,
68
+ exitCode: last.code,
69
+ output: last.output,
70
+ });
71
+ }
72
+ return results;
73
+ }
74
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,86 @@
1
+ /**
2
+ * `harness runstatus` — the run-status overlay's write + read sides.
3
+ *
4
+ * publish [--file <path>] --spool <dir> [--machine <id>]
5
+ * push a run-status document into the hub spool (P7.1). Doc from `--file`
6
+ * or stdin. `machine_id` is stamped from `--machine`, else
7
+ * `HARNESS_MACHINE_ID`, else the OS hostname — a value in the document is
8
+ * overridden.
9
+ * project --spool <dir> [--redact] [--stale-after <ms>] [--expire-after <ms>]
10
+ * classify the spool into the board overlay (P7.2): live | stale; retired
11
+ * runs (terminal outcome or past expiry) are dropped. `--redact` strips
12
+ * needs-you reason text (P5.3 rule).
13
+ *
14
+ * Exit 0 on success, 2 on a bad document / missing spool.
15
+ */
16
+ import { readFileSync } from "node:fs";
17
+ import { hostname } from "node:os";
18
+ import { projectRuns, publishRunStatus, RunStatusError } from "./index.js";
19
+ function flag(args, name) {
20
+ const i = args.indexOf(`--${name}`);
21
+ return i >= 0 && args[i + 1] ? args[i + 1] : undefined;
22
+ }
23
+ export function runstatusCommand(args) {
24
+ const sub = args[0];
25
+ if (sub !== "publish" && sub !== "project") {
26
+ process.stderr.write("harness runstatus <publish [--file <path>] --spool <dir> [--machine <id>] | project --spool <dir> [--redact] [--stale-after <ms>] [--expire-after <ms>]>\n");
27
+ return 2;
28
+ }
29
+ if (sub === "project") {
30
+ const spool = flag(args, "spool");
31
+ if (!spool) {
32
+ process.stderr.write("runstatus project: --spool <dir> is required\n");
33
+ return 2;
34
+ }
35
+ const staleAfterMs = flag(args, "stale-after");
36
+ const expireAfterMs = flag(args, "expire-after");
37
+ const overlay = projectRuns(spool, {
38
+ redact: args.includes("--redact"),
39
+ ...(staleAfterMs !== undefined ? { staleAfterMs: Number(staleAfterMs) } : {}),
40
+ ...(expireAfterMs !== undefined ? { expireAfterMs: Number(expireAfterMs) } : {}),
41
+ });
42
+ process.stdout.write(JSON.stringify(overlay, null, 2) + "\n");
43
+ return 0;
44
+ }
45
+ const spoolDir = flag(args, "spool");
46
+ if (!spoolDir) {
47
+ process.stderr.write("runstatus publish: --spool <dir> is required\n");
48
+ return 2;
49
+ }
50
+ const machineId = flag(args, "machine") ?? process.env.HARNESS_MACHINE_ID ?? safeHostname();
51
+ const file = flag(args, "file");
52
+ let text;
53
+ try {
54
+ text = file ? readFileSync(file, "utf8") : readFileSync(0, "utf8");
55
+ }
56
+ catch (e) {
57
+ process.stderr.write(`runstatus publish: cannot read document: ${e.message}\n`);
58
+ return 2;
59
+ }
60
+ let doc;
61
+ try {
62
+ doc = JSON.parse(text);
63
+ }
64
+ catch (e) {
65
+ process.stderr.write(`runstatus publish: document is not JSON: ${e.message}\n`);
66
+ return 2;
67
+ }
68
+ try {
69
+ const r = publishRunStatus(doc, { spoolDir, machineId });
70
+ process.stdout.write(`published ${r.key} → ${r.path}\n`);
71
+ return 0;
72
+ }
73
+ catch (e) {
74
+ process.stderr.write(`${e.message}\n`);
75
+ return e instanceof RunStatusError ? 2 : 1;
76
+ }
77
+ }
78
+ function safeHostname() {
79
+ try {
80
+ return hostname() || "local";
81
+ }
82
+ catch {
83
+ return "local";
84
+ }
85
+ }
86
+ //# sourceMappingURL=cli.js.map