tamperward 2.32.0 → 2.33.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.
package/README.md CHANGED
@@ -239,11 +239,13 @@ node harness/perf/bench.mjs # performance budgets (docs/PERF.md)
239
239
  ```
240
240
 
241
241
  Tamperward's own CI runs the engine it ships over every pull request — `check --diff`
242
- over the PR range, cleared only by an out-of-band label — and has, on more than one
242
+ over the PR range, cleared only by an out-of-band exact-head label (generate one with
243
+ `tamperward signoff-label`) — and has, on more than one
243
244
  occasion, blocked its own author's commits. Branch, then open a PR; `main` is
244
245
  protected and CI must be green. Changing a protected asset will block your own PR
245
246
  (working as intended); a reviewed, legitimate change is cleared by a maintainer
246
- applying a `tamperward:allow:<rule>` label, never by weakening the policy.
247
+ applying a compact `tw1:<digest>` label, never by weakening the policy. Legacy full-SHA
248
+ tokens remain accepted by the parser for CI systems whose label transport can hold them.
247
249
 
248
250
  The public surface is the CLI and its exit codes, the hook wire format, the
249
251
  `.tamperward.yml` schema, and the versioned machine-output schemas under `schemas/` —
package/dist/cli/index.js CHANGED
@@ -8932,7 +8932,7 @@ They WILL FAIL in v10.0.0
8932
8932
  makeFinding(RULE9, policy, {
8933
8933
  file: c.path,
8934
8934
  message: `A protected hook script was added that does not run the gate live: ${why}. A new gate script needs a sign-off unless the gate is live in it.`,
8935
- evidence: `a hook script was added; sign off with \`tamperward allow hook-tampering --file ${c.path} --reason "..."\` locally or the \`tamperward:allow:hook-tampering@<head-sha>\` label in CI, or make the gate live in it`,
8935
+ evidence: `a hook script was added; sign off with \`tamperward allow hook-tampering --file ${c.path} --reason "..."\` locally or generate the compact CI label with \`tamperward signoff-label --rule hook-tampering --file ${c.path} --head <full-sha>\`, or make the gate live in it`,
8936
8936
  remediation: "Run `tamperward check --staged` in the new hook, in a position where its failure fails the hook, or have a human sign off on a hook that does not."
8937
8937
  })
8938
8938
  );
@@ -8963,7 +8963,7 @@ They WILL FAIL in v10.0.0
8963
8963
  makeFinding(RULE9, policy, {
8964
8964
  file: c.path,
8965
8965
  message: `A hand-written protected hook script was changed: ${detail.length ? detail.join("; ") : "an edit other than a pin raise"}. Every edit to a gate script other than raising its pin needs a sign-off.`,
8966
- evidence: `the gate script changed; sign off with \`tamperward allow hook-tampering --file ${c.path} --reason "..."\` locally or the \`tamperward:allow:hook-tampering@<head-sha>\` label in CI, or restore it`,
8966
+ evidence: `the gate script changed; sign off with \`tamperward allow hook-tampering --file ${c.path} --reason "..."\` locally or generate the compact CI label with \`tamperward signoff-label --rule hook-tampering --file ${c.path} --head <full-sha>\`, or restore it`,
8967
8967
  remediation: "Restore the script, or have a human sign off. A line-by-line reading of a shell script cannot tell an honest restructuring from a neutered gate, so the gate script is held byte-for-byte: only raising its pin passes without a sign-off."
8968
8968
  })
8969
8969
  );
@@ -10869,6 +10869,12 @@ var init_policy_load = __esm({
10869
10869
  import { createHash as createHash3 } from "node:crypto";
10870
10870
  import { appendFileSync, mkdirSync, readFileSync as readFileSync6, existsSync as existsSync6 } from "node:fs";
10871
10871
  import { dirname as dirname2, join as join9 } from "node:path";
10872
+ function compactOobToken(want, head) {
10873
+ const normalizedHead = head.trim().toLowerCase();
10874
+ if (!want || !FULL_OBJECT_ID.test(normalizedHead)) return null;
10875
+ const digest = createHash3("sha256").update(`tamperward:oob:v1\0${want}\0${normalizedHead}`).digest("base64url");
10876
+ return `${COMPACT_OOB_PREFIX}${digest}`;
10877
+ }
10872
10878
  function ledgerEntryFrom(value) {
10873
10879
  if (!isRecord(value)) return null;
10874
10880
  const { rule, file, fingerprint: fingerprint2, reason, recordedAt, expiresAt } = value;
@@ -10924,6 +10930,11 @@ function oobToken(want, oob, head) {
10924
10930
  for (const raw of oob) {
10925
10931
  const t = raw.trim();
10926
10932
  if (!t) continue;
10933
+ if (t.startsWith(COMPACT_OOB_PREFIX)) {
10934
+ const expected = head === void 0 ? null : compactOobToken(want, head);
10935
+ if (expected !== null && t === expected) return t;
10936
+ continue;
10937
+ }
10927
10938
  const at = t.lastIndexOf("@");
10928
10939
  if (at === -1) {
10929
10940
  if (!head && t === want) return t;
@@ -10956,13 +10967,15 @@ function oobHeadFromEnv(env = process.env) {
10956
10967
  function oobFromEnv(env = process.env) {
10957
10968
  return (env.TAMPERWARD_OOB_SIGNOFF ?? "").split(",").map((s) => s.trim().replace(/^tamperward:allow:/, "")).filter(Boolean);
10958
10969
  }
10959
- var DEFAULT_TTL_MS, fingerprintOf;
10970
+ var DEFAULT_TTL_MS, COMPACT_OOB_PREFIX, FULL_OBJECT_ID, fingerprintOf;
10960
10971
  var init_signoff = __esm({
10961
10972
  "src/signoff.ts"() {
10962
10973
  "use strict";
10963
10974
  init_policy_load();
10964
10975
  init_narrow();
10965
10976
  DEFAULT_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
10977
+ COMPACT_OOB_PREFIX = "tw1:";
10978
+ FULL_OBJECT_ID = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i;
10966
10979
  fingerprintOf = (f) => fingerprint(f.rule, f.file, f.evidence);
10967
10980
  }
10968
10981
  });
@@ -14206,9 +14219,10 @@ version: 1
14206
14219
 
14207
14220
  # The CI authority for main: the same engine as the agent hook and pre-commit, run over
14208
14221
  # the PR's commit range. A block fails the check and clears ONLY via the out-of-band
14209
- # label \`tamperward:allow:<rule>@<head-sha>\` applied by someone with triage access or higher \u2014
14210
- # never a file the PR itself can commit. The verify step reads the same labels:
14211
- # \`tamperward:allow:verify@<head-sha>\` accepts a masked failure a reviewer has judged.
14222
+ # compact label \`tw1:<digest>\` (print one with \`tamperward signoff-label\`) applied by
14223
+ # someone with triage access or higher \u2014 never a file the PR itself can commit. Legacy
14224
+ # \`tamperward:allow:<rule>@<head-sha>\` labels remain accepted. The verify step reads
14225
+ # the same labels: a token for \`verify\` accepts a masked failure a reviewer has judged.
14212
14226
  #
14213
14227
  # labeled/unlabeled re-run the gate because the sign-off is read from the EVENT payload:
14214
14228
  # a label applied after a failure could otherwise never take effect, and REVOKING a
@@ -14252,14 +14266,15 @@ jobs:
14252
14266
  env:
14253
14267
  LABELS: \${{ toJSON(github.event.pull_request.labels.*.name) }}
14254
14268
  run: |
14255
- RULES="$(printf '%s' "$LABELS" | jq -r '.[] | select(startswith("tamperward:allow:")) | sub("^tamperward:allow:"; "")' | paste -sd, -)"
14269
+ RULES="$(printf '%s' "$LABELS" | jq -r '.[] | if startswith("tamperward:allow:") then sub("^tamperward:allow:"; "") elif startswith("tw1:") then . else empty end' | paste -sd, -)"
14256
14270
  echo "rules=$RULES" >> "$GITHUB_OUTPUT"
14257
14271
  - name: Tamperward gate (diff-time)
14258
14272
  env:
14259
14273
  TAMPERWARD_OOB_SIGNOFF: \${{ steps.oob.outputs.rules }}
14260
14274
  # Binds each approval to the commit it was granted for: labels persist
14261
14275
  # across pushes, so an unbound one would clear every later finding on
14262
- # the same PR. Labels must read tamperward:allow:<rule>@<head-sha>.
14276
+ # the same PR. Compact labels hash the exact rule/file/head tuple; legacy
14277
+ # labels must read tamperward:allow:<rule>@<head-sha>.
14263
14278
  TAMPERWARD_OOB_HEAD: \${{ github.event.pull_request.head.sha }}
14264
14279
  run: tamperward check --diff "\${{ github.event.pull_request.base.sha }}...\${{ github.event.pull_request.head.sha }}"
14265
14280
  # Diff-time detection is spelling-dependent by nature; pristine
@@ -14272,8 +14287,9 @@ jobs:
14272
14287
  # without one this step fails closed (exit 2) rather than passing quietly.
14273
14288
  - name: Tamperward verify (pristine re-execution)
14274
14289
  env:
14275
- # The same channel as the gate. \`tamperward:allow:verify@<head-sha>\`
14276
- # accepts a MASKED_FAILURE \u2014 a reviewer has read the intentional test
14290
+ # The same channel as the gate. A compact token generated for \`verify\`
14291
+ # (or legacy \`tamperward:allow:verify@<head-sha>\`) accepts a MASKED_FAILURE \u2014
14292
+ # a reviewer has read the intentional test
14277
14293
  # change and agrees the original suite no longer applies. It clears
14278
14294
  # nothing else: a red visible suite, or a run that could not verify,
14279
14295
  # stays red whatever the labels say.
@@ -16398,10 +16414,12 @@ function runVerify(opts) {
16398
16414
  renderStageDiagnostics(out3, "pristine", pristine);
16399
16415
  }
16400
16416
  }
16401
- if (signedOff)
16417
+ if (signedOff) {
16418
+ const approval = signedOff.startsWith("tw1:") ? signedOff : `tamperward:allow:${signedOff}`;
16402
16419
  out3(
16403
- `masked failure cleared by out-of-band approval (tamperward:allow:${signedOff}): a reviewer accepted that the original suite no longer applies to this change. Exit 0.`
16420
+ `masked failure cleared by out-of-band approval (${approval}): a reviewer accepted that the original suite no longer applies to this change. Exit 0.`
16404
16421
  );
16422
+ }
16405
16423
  if (removedAdded > 0)
16406
16424
  out3(
16407
16425
  `(${removedAdded} protected file(s) added since ${base.slice(0, 10)} were removed from the pristine run: the pristine tree carries exactly the base's protected surface.)`
@@ -20438,6 +20456,33 @@ var init_research = __esm({
20438
20456
  }
20439
20457
  });
20440
20458
 
20459
+ // src/cli/signoff-label.ts
20460
+ function runSignoffLabel(opts) {
20461
+ if (!opts.rule) {
20462
+ process.stderr.write("tamperward signoff-label --rule <rule> --head <full-head-sha> [--file <path>]\n");
20463
+ return 2;
20464
+ }
20465
+ if (!opts.head) {
20466
+ process.stderr.write("tamperward: --head is required and must be a full 40- or 64-character object id.\n");
20467
+ return 2;
20468
+ }
20469
+ const want = opts.file ? `${opts.rule}:${opts.file}` : opts.rule;
20470
+ const token = compactOobToken(want, opts.head);
20471
+ if (!token) {
20472
+ process.stderr.write("tamperward: --head must be a full 40- or 64-character hexadecimal object id.\n");
20473
+ return 2;
20474
+ }
20475
+ process.stdout.write(`${token}
20476
+ `);
20477
+ return 0;
20478
+ }
20479
+ var init_signoff_label = __esm({
20480
+ "src/cli/signoff-label.ts"() {
20481
+ "use strict";
20482
+ init_signoff();
20483
+ }
20484
+ });
20485
+
20441
20486
  // src/cli/main.ts
20442
20487
  var main_exports = {};
20443
20488
  __export(main_exports, {
@@ -20517,6 +20562,16 @@ function parseStats(args) {
20517
20562
  }
20518
20563
  return o;
20519
20564
  }
20565
+ function parseSignoffLabel(args) {
20566
+ const o = {};
20567
+ for (let i = 0; i < args.length; i++) {
20568
+ const a = args[i];
20569
+ if (a === "--rule") o.rule = args[++i];
20570
+ else if (a === "--file") o.file = args[++i];
20571
+ else if (a === "--head") o.head = args[++i];
20572
+ }
20573
+ return o;
20574
+ }
20520
20575
  function parseCheck(args) {
20521
20576
  const o = {};
20522
20577
  for (let i = 0; i < args.length; i++) {
@@ -20638,6 +20693,11 @@ function validateCliArgs(cmd, args) {
20638
20693
  if (parsed.positionals.length === 0) return "allow requires a rule";
20639
20694
  return void 0;
20640
20695
  }
20696
+ if (cmd === "signoff-label") {
20697
+ return validateFlatArgs(args, {
20698
+ values: { "--rule": "string", "--file": "string", "--head": "string" }
20699
+ }).error;
20700
+ }
20641
20701
  if (cmd === "init") {
20642
20702
  return validateFlatArgs(args, {
20643
20703
  flags: ["--dry-run", "--force-workflow"],
@@ -20790,8 +20850,8 @@ Formats:
20790
20850
  [--budget S] [--json] [--keep] as-is AND with protected files restored
20791
20851
  [--require-ancestor] [--cwd D] from the trusted base; a visible-green /
20792
20852
  pristine-red result is a MASKED FAILURE
20793
- (exit 1, or 0 under an out-of-band
20794
- verify@<head-sha> approval); cannot-verify
20853
+ (exit 1, or 0 under a compact tw1:<digest>
20854
+ or legacy verify@<full-sha> approval); cannot-verify
20795
20855
  fails closed (2)
20796
20856
  tamperward trace-verify [--base R] advisory Linux verifier-input discovery:
20797
20857
  [--cmd C] [--budget S] [--runs N] trace a trusted/known-good base with
@@ -20836,6 +20896,8 @@ Formats:
20836
20896
  integrity signal, not proof of agent intent.
20837
20897
  tamperward allow <rule> --reason "..." record a human sign-off (local audit ledger)
20838
20898
  [--file F] [--cwd D]
20899
+ tamperward signoff-label --rule <rule> --head <sha> [--file F]
20900
+ print a compact, exact-head-bound CI label
20839
20901
  tamperward onboard [--yes] [--cwd D] guided first-run setup: preflight, the
20840
20902
  [--base R] [--repo O/R] init plan with a confirmation before any
20841
20903
  [--branch B] [--verify-command C] write, canonical init, explicit verifier
@@ -20900,6 +20962,8 @@ function main(argv) {
20900
20962
  return runAgentCommand("sweep", rest);
20901
20963
  case "allow":
20902
20964
  return runAllow(parseAllow(rest));
20965
+ case "signoff-label":
20966
+ return runSignoffLabel(parseSignoffLabel(rest));
20903
20967
  case "init":
20904
20968
  return runInit(parseInit(rest));
20905
20969
  case "doctor":
@@ -20968,6 +21032,7 @@ var init_main = __esm({
20968
21032
  init_onboard();
20969
21033
  init_research();
20970
21034
  init_audit();
21035
+ init_signoff_label();
20971
21036
  }
20972
21037
  });
20973
21038
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tamperward",
3
- "version": "2.32.0",
3
+ "version": "2.33.0",
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",