tamperward 2.39.0 → 2.39.2

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 (2) hide show
  1. package/dist/cli/index.js +50 -33
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -19896,6 +19896,17 @@ var init_runtimes = __esm({
19896
19896
  });
19897
19897
 
19898
19898
  // src/adapters/contract.ts
19899
+ function immutableRuntimeCapabilities(caps) {
19900
+ return Object.freeze({
19901
+ preDeny: Object.freeze([...caps.preDeny]),
19902
+ postObserve: Object.freeze([...caps.postObserve]),
19903
+ endOfTurn: caps.endOfTurn,
19904
+ unsupported: Object.freeze([...caps.unsupported])
19905
+ });
19906
+ }
19907
+ function immutableRuntimeAdapter(adapter) {
19908
+ return Object.freeze(adapter);
19909
+ }
19899
19910
  function unknownToolFinding(toolName) {
19900
19911
  const name = toolName.length > 80 ? toolName.slice(0, 77) + "..." : toolName;
19901
19912
  const detail = 'Copilot tool "' + name + `" is not in TamperWard's explicit operation vocabulary`;
@@ -19963,23 +19974,18 @@ var init_adapter = __esm({
19963
19974
  init_contract();
19964
19975
  ClaudeRuntimeAdapter = class {
19965
19976
  name = "claude-code";
19966
- /**
19967
- * Claude Code's live PreToolUse fires for EVERY tool and can synchronously deny it —
19968
- * shell, file edit, file read, MCP, and anything else — even under
19969
- * `--dangerously-skip-permissions`. The end-of-turn Stop sweep is live too. There is no
19970
- * live per-tool PostToolUse veto (Stop is the post-turn reconciliation), so `postObserve`
19971
- * is empty and `unsupported` names that gap explicitly rather than implying it.
19972
- */
19973
- capabilities = {
19974
- preDeny: OPERATION_KINDS,
19977
+ capabilities = immutableRuntimeCapabilities({
19978
+ preDeny: OPERATION_KINDS.filter((kind) => kind !== "mcp" && kind !== "other"),
19975
19979
  postObserve: [],
19976
19980
  endOfTurn: true,
19977
19981
  unsupported: [
19982
+ "MCP pre-deny is not declared because Claude MCP calls are not reconstructed into Change[] before evaluation",
19983
+ "catch-all other-tool pre-deny is not declared because unmodelled Claude tools are not reconstructed into Change[] before evaluation",
19978
19984
  "per-operation post-action veto (end-of-turn Stop sweep is the post-turn reconciliation)",
19979
19985
  "network-egress control",
19980
19986
  "identity / authentication"
19981
19987
  ]
19982
- };
19988
+ });
19983
19989
  parseEvent(raw, phase) {
19984
19990
  try {
19985
19991
  return eventFrom(parseInput(raw), phase);
@@ -20041,26 +20047,28 @@ var init_adapter = __esm({
20041
20047
  const parsed = this.parseEvent(raw, phase);
20042
20048
  if ("failure" in parsed) {
20043
20049
  const live2 = phase === "end-of-turn" ? stopFromRaw(raw, defaultCwd) : preToolUseFromRaw(raw, defaultCwd);
20050
+ const findings2 = live2.findings ? [...live2.findings] : [];
20044
20051
  return {
20045
20052
  outcome: "parse-failure",
20046
20053
  detail: parsed.detail,
20047
20054
  wire: live2.stdout,
20048
- decision: { verdict: "deny", findings: [], reason: live2.stdout }
20055
+ decision: { verdict: "deny", findings: findings2, reason: live2.stdout }
20049
20056
  };
20050
20057
  }
20051
20058
  const idv = this.validateIdentity(parsed.identity, defaultCwd);
20052
20059
  if (!idv.ok) {
20053
- const findings = [steeringUnavailableFinding(`repository identity claim rejected: ${idv.rejected}`)];
20054
- const wire2 = this.denyPayload(findings, phase);
20055
- return { outcome: "ok", wire: wire2, detail: idv.rejected, decision: { verdict: "deny", findings, reason: wire2 } };
20060
+ const findings2 = [steeringUnavailableFinding(`repository identity claim rejected: ${idv.rejected}`)];
20061
+ const wire2 = this.denyPayload(findings2, phase);
20062
+ return { outcome: "ok", wire: wire2, detail: idv.rejected, decision: { verdict: "deny", findings: findings2, reason: wire2 } };
20056
20063
  }
20057
20064
  const live = phase === "end-of-turn" ? stopFromRaw(raw, defaultCwd, idv.trustedRoot) : preToolUseFromRaw(raw, defaultCwd, idv.trustedRoot);
20058
20065
  const wire = live.stdout;
20059
20066
  const denied = wire.length > 0;
20067
+ const findings = live.findings ? [...live.findings] : [];
20060
20068
  return {
20061
20069
  outcome: "ok",
20062
20070
  wire,
20063
- decision: { verdict: denied ? "deny" : "allow", findings: [], reason: denied ? wire : void 0 }
20071
+ decision: { verdict: denied ? "deny" : "allow", findings, reason: denied ? wire : void 0 }
20064
20072
  };
20065
20073
  }
20066
20074
  /**
@@ -20074,7 +20082,7 @@ var init_adapter = __esm({
20074
20082
  return failClosedResult(outcome, detail, phase, (f, p) => this.denyPayload(f, p));
20075
20083
  }
20076
20084
  };
20077
- claudeAdapter = new ClaudeRuntimeAdapter();
20085
+ claudeAdapter = immutableRuntimeAdapter(new ClaudeRuntimeAdapter());
20078
20086
  }
20079
20087
  });
20080
20088
 
@@ -20376,7 +20384,7 @@ var init_adapter2 = __esm({
20376
20384
  * stop, so those are declared; every real gap is named in `unsupported`. The
20377
20385
  * `probe:codex-runtime` harness is what may later justify moving a kind into `preDeny`.
20378
20386
  */
20379
- capabilities = {
20387
+ capabilities = immutableRuntimeCapabilities({
20380
20388
  preDeny: [],
20381
20389
  postObserve: POST_OBSERVE,
20382
20390
  endOfTurn: true,
@@ -20386,7 +20394,7 @@ var init_adapter2 = __esm({
20386
20394
  "network-egress control",
20387
20395
  "identity / authentication"
20388
20396
  ]
20389
- };
20397
+ });
20390
20398
  parseEvent(raw, phase) {
20391
20399
  return normalizeCodexEvent(raw, phase);
20392
20400
  }
@@ -20439,8 +20447,9 @@ var init_adapter2 = __esm({
20439
20447
  return { outcome: "ok", wire, detail: idv.rejected, decision: { verdict: "deny", findings, reason: wire } };
20440
20448
  }
20441
20449
  if (phase === "end-of-turn") {
20442
- const wire = stopFromRaw(raw, defaultCwd, idv.trustedRoot).stdout;
20443
- return { outcome: "ok", wire, decision: { verdict: wire ? "deny" : "allow", findings: [], reason: wire || void 0 } };
20450
+ const swept = stopFromRaw(raw, defaultCwd, idv.trustedRoot);
20451
+ const findings = swept.findings ? [...swept.findings] : [];
20452
+ return { outcome: "ok", wire: swept.stdout, decision: { verdict: swept.stdout ? "deny" : "allow", findings, reason: swept.stdout || void 0 } };
20444
20453
  }
20445
20454
  try {
20446
20455
  const root = idv.trustedRoot ?? repoRoot(defaultCwd ?? process.cwd());
@@ -20464,7 +20473,7 @@ var init_adapter2 = __esm({
20464
20473
  return failClosedResult(outcome, detail, phase, (f, p) => this.denyPayload(f, p));
20465
20474
  }
20466
20475
  };
20467
- codexAdapter = new CodexRuntimeAdapter();
20476
+ codexAdapter = immutableRuntimeAdapter(new CodexRuntimeAdapter());
20468
20477
  }
20469
20478
  });
20470
20479
 
@@ -20705,7 +20714,7 @@ var init_adapter3 = __esm({
20705
20714
  * does not report. Every real gap is named in `unsupported`. The `probe:copilot-runtime`
20706
20715
  * harness is what may later justify moving a kind into `preDeny` or populating `postObserve`.
20707
20716
  */
20708
- capabilities = {
20717
+ capabilities = immutableRuntimeCapabilities({
20709
20718
  preDeny: [],
20710
20719
  postObserve: [],
20711
20720
  endOfTurn: true,
@@ -20719,7 +20728,7 @@ var init_adapter3 = __esm({
20719
20728
  "network-egress control",
20720
20729
  "identity / authentication"
20721
20730
  ]
20722
- };
20731
+ });
20723
20732
  parseEvent(raw, phase) {
20724
20733
  return normalizeCopilotEvent(raw, phase);
20725
20734
  }
@@ -20778,12 +20787,13 @@ var init_adapter3 = __esm({
20778
20787
  if (phase === "end-of-turn") {
20779
20788
  const stopInput = copilotStopInput(raw);
20780
20789
  if (typeof stopInput !== "string") {
20781
- const findings = [steeringUnavailableFinding(`unparseable Copilot event: ${stopInput.detail}`)];
20782
- const wire2 = this.denyPayload(findings, "end-of-turn");
20783
- return { outcome: "parse-failure", detail: stopInput.detail, wire: wire2, decision: { verdict: "deny", findings, reason: wire2 } };
20790
+ const findings2 = [steeringUnavailableFinding(`unparseable Copilot event: ${stopInput.detail}`)];
20791
+ const wire = this.denyPayload(findings2, "end-of-turn");
20792
+ return { outcome: "parse-failure", detail: stopInput.detail, wire, decision: { verdict: "deny", findings: findings2, reason: wire } };
20784
20793
  }
20785
- const wire = stopFromRaw(stopInput, defaultCwd, idv.trustedRoot).stdout;
20786
- return { outcome: "ok", wire, decision: { verdict: wire ? "deny" : "allow", findings: [], reason: wire || void 0 } };
20794
+ const swept = stopFromRaw(stopInput, defaultCwd, idv.trustedRoot);
20795
+ const findings = swept.findings ? [...swept.findings] : [];
20796
+ return { outcome: "ok", wire: swept.stdout, decision: { verdict: swept.stdout ? "deny" : "allow", findings, reason: swept.stdout || void 0 } };
20787
20797
  }
20788
20798
  if (parsed.operation.kind === "unknown") {
20789
20799
  const detail = `unrecognized Copilot tool ${parsed.operation.name}; refusing to evaluate it as a no-op`;
@@ -20813,7 +20823,7 @@ var init_adapter3 = __esm({
20813
20823
  return failClosedResult(outcome, detail, phase, (f, p) => this.denyPayload(f, p));
20814
20824
  }
20815
20825
  };
20816
- copilotAdapter = new CopilotRuntimeAdapter();
20826
+ copilotAdapter = immutableRuntimeAdapter(new CopilotRuntimeAdapter());
20817
20827
  }
20818
20828
  });
20819
20829
 
@@ -21151,7 +21161,7 @@ var init_adapter4 = __esm({
21151
21161
  * no post-execution veto). `endOfTurn` is true via `onAgentStop`. Every real gap is named in
21152
21162
  * `unsupported`, at the granularity #611 asks for.
21153
21163
  */
21154
- capabilities = {
21164
+ capabilities = immutableRuntimeCapabilities({
21155
21165
  preDeny: [],
21156
21166
  postObserve: [],
21157
21167
  endOfTurn: true,
@@ -21163,7 +21173,7 @@ var init_adapter4 = __esm({
21163
21173
  "network-egress control",
21164
21174
  "identity / authentication"
21165
21175
  ]
21166
- };
21176
+ });
21167
21177
  parseEvent(raw, phase) {
21168
21178
  return normalizeCopilotSdkEvent(raw, phase);
21169
21179
  }
@@ -21258,7 +21268,7 @@ var init_adapter4 = __esm({
21258
21268
  return failClosedResult(outcome, detail, phase, (f, p) => this.denyPayload(f, p));
21259
21269
  }
21260
21270
  };
21261
- copilotSdkAdapter = new CopilotSdkHostedAdapter();
21271
+ copilotSdkAdapter = immutableRuntimeAdapter(new CopilotSdkHostedAdapter());
21262
21272
  }
21263
21273
  });
21264
21274
 
@@ -21985,7 +21995,8 @@ function storedQualification(adapter, cwd, opts = {}) {
21985
21995
  const validated = validateStoredReport(raw);
21986
21996
  if ("reason" in validated) return { state: "rejected", reason: validated.reason };
21987
21997
  const stored = validated.report;
21988
- const { binding: current } = buildQualification(adapter, opts, cwd);
21998
+ const currentQualification = buildQualification(adapter, opts, cwd);
21999
+ const current = currentQualification.binding;
21989
22000
  const storedBinding = {
21990
22001
  runtime: stored.runtime,
21991
22002
  tamperward: stored.tamperward,
@@ -21999,6 +22010,12 @@ function storedQualification(adapter, cwd, opts = {}) {
21999
22010
  evidence_id: stored.evidence_id
22000
22011
  };
22001
22012
  const staleness = qualificationStaleness(storedBinding, current);
22013
+ if (!staleness.stale && (stored.evidence_id !== current.evidence_id || JSON.stringify(stored.capabilities) !== JSON.stringify(currentQualification.assessments))) {
22014
+ return {
22015
+ state: "rejected",
22016
+ reason: "stored qualification posture does not match retained evidence for the current binding"
22017
+ };
22018
+ }
22002
22019
  return { state: "recorded", report: stored, stale: staleness.stale, changed_inputs: staleness.changed };
22003
22020
  }
22004
22021
  function runRuntime(sub, opts) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tamperward",
3
- "version": "2.39.0",
3
+ "version": "2.39.2",
4
4
  "description": "The deterministic agent-integrity gate. One ruleset, evaluated on the actual diff/commands as a verdict, enforced everywhere a change can be made.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "hexrift",