@maintainabilityai/research-runner 0.1.41 → 0.1.42

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.
@@ -1881,6 +1881,12 @@ const handleAuditVerifyChain = async (input) => {
1881
1881
  // Track signature state across the whole chain.
1882
1882
  let signedCount = 0;
1883
1883
  let workflowUnsignedCount = 0; // post-agent workflow-emitted events, unsigned by-design
1884
+ // P9 (Bug-P / Codex audit): revise-agent unsigned events get their own
1885
+ // bucket so we can decide legitimacy chain-by-chain — legacy chains
1886
+ // (no per-epoch signing anywhere) keep the old allowance; per-epoch
1887
+ // chains (any event with `signer_epoch`) require revise-agent to sign.
1888
+ let reviseAgentUnsignedCount = 0;
1889
+ let chainUsesPerEpochSigning = false;
1884
1890
  let prev = null;
1885
1891
  for (let i = 0; i < lines.length; i++) {
1886
1892
  let event;
@@ -1911,23 +1917,52 @@ const handleAuditVerifyChain = async (input) => {
1911
1917
  if (recordedHash !== recomputed) {
1912
1918
  return { ok: false, reason: `forged-hash-line-${i + 1}: recorded=${recordedHash.slice(0, 16)}… recomputed=${recomputed.slice(0, 16)}…` };
1913
1919
  }
1914
- // Bug K + N (cert-run-5): post-agent events (payload.emitted_by in
1915
- // {'workflow', 'revise-agent'}) carry signature: '' by design the
1916
- // post-agent context can't sign because the ephemeral private key is
1917
- // gone. Count them separately so they don't trip partial-tampering
1918
- // detection or signature verification. Both attributions are
1919
- // legitimate-unsigned.
1920
+ // Bug K + N (cert-run-5): post-agent events emitted by the workflow
1921
+ // (e.g. the synthetic self_review backfill that runs AFTER the agent
1922
+ // session ended) genuinely cannot sign the ephemeral private key
1923
+ // is gone by then. `payload.emitted_by: 'workflow'` is the legitimate
1924
+ // unsigned attribution.
1925
+ //
1926
+ // P9 (Bug-P / Codex audit) — `revise-agent` used to share the same
1927
+ // legitimate-unsigned bucket because Bug N landed BEFORE Bug O. With
1928
+ // per-epoch signing (Bug O), a revise-agent session DOES have an
1929
+ // ephemeral key and DOES sign its events. So an unsigned revise-agent
1930
+ // event is now only legitimate on LEGACY chains — chains where no
1931
+ // event carries `signer_epoch`. Tracking that requires a chain-level
1932
+ // verdict, so we count unsigned revise-agent events into a separate
1933
+ // bucket and decide legitimacy after the loop sees whether the chain
1934
+ // uses per-epoch signing at all.
1920
1935
  const eventPayload = event.payload;
1921
1936
  const emittedBy = eventPayload?.emitted_by;
1922
- const isUnsignedByDesign = emittedBy === 'workflow' || emittedBy === 'revise-agent';
1937
+ const isWorkflowUnsigned = emittedBy === 'workflow';
1938
+ const isReviseAgentUnsigned = emittedBy === 'revise-agent';
1939
+ if (typeof event.signer_epoch === 'number') {
1940
+ chainUsesPerEpochSigning = true;
1941
+ }
1923
1942
  if (recordedSignature !== null && recordedSignature !== '') {
1924
1943
  signedCount++;
1925
1944
  }
1926
- else if (isUnsignedByDesign) {
1945
+ else if (isWorkflowUnsigned) {
1927
1946
  workflowUnsignedCount++;
1928
1947
  }
1948
+ else if (isReviseAgentUnsigned) {
1949
+ reviseAgentUnsignedCount++;
1950
+ }
1929
1951
  prev = recordedHash;
1930
1952
  }
1953
+ // P9: legacy chains (pre-Bug-O — no signer_epoch on any event) keep
1954
+ // the broad allowance. New chains (any event carries signer_epoch)
1955
+ // require revise-agent events to be signed; an unsigned revise-agent
1956
+ // event on a per-epoch chain is now a real chain-integrity failure.
1957
+ if (chainUsesPerEpochSigning && reviseAgentUnsignedCount > 0) {
1958
+ return {
1959
+ ok: false,
1960
+ reason: `revise-agent-unsigned-on-per-epoch-chain: ${reviseAgentUnsignedCount} revise-agent events without signatures; per-epoch chains require revise-agent to sign with its own epoch key (Bug O contract)`,
1961
+ };
1962
+ }
1963
+ // Legacy chains: roll revise-agent unsigned into the workflow-unsigned
1964
+ // bucket so the downstream "agent_event_count" math still excludes them.
1965
+ workflowUnsignedCount += reviseAgentUnsignedCount;
1931
1966
  // Knight's Seal verification: every AGENT-emitted event must be signed
1932
1967
  // by its declared signer_epoch's pub key. Workflow-unsigned events are
1933
1968
  // excluded from the denominator (their emitted_by: 'workflow' marker
@@ -1945,9 +1980,14 @@ const handleAuditVerifyChain = async (input) => {
1945
1980
  for (let i = 0; i < lines.length; i++) {
1946
1981
  const event = JSON.parse(lines[i]);
1947
1982
  const emittedBy = event.payload?.emitted_by;
1948
- const isLegacyUnsigned = (emittedBy === 'workflow' || emittedBy === 'revise-agent')
1983
+ // P9: workflow-emitted unsigned events are always legitimate
1984
+ // (the post-agent context genuinely has no private key). For
1985
+ // revise-agent unsigned events, the loop above already returned
1986
+ // an error if we're on a per-epoch chain, so reaching this point
1987
+ // means we're on a legacy chain where the looser bucket applies.
1988
+ const isLegitimateUnsigned = (emittedBy === 'workflow' || emittedBy === 'revise-agent')
1949
1989
  && (!event.signature || event.signature === '');
1950
- if (isLegacyUnsigned) {
1990
+ if (isLegitimateUnsigned) {
1951
1991
  continue;
1952
1992
  }
1953
1993
  // Bug O (Task #72) — per-epoch verification. Events default to
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maintainabilityai/research-runner",
3
- "version": "0.1.41",
3
+ "version": "0.1.42",
4
4
  "description": "Research + PRD agent runner — orchestrates the Archeologist and PRD pipelines for the MaintainabilityAI governance mesh",
5
5
  "license": "MIT",
6
6
  "author": "MaintainabilityAI",