Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- Add a Noema-owned exact-claim evidence receipt contract whose execution and research producers serialize one canonical artifact that binds every receipt semantic field, including command/result/isolation/network or source revision/excerpt/retrieval policy. Admission accepts only a receipt ID from untrusted model output. The owner API first verifies the exact authenticated OpenCode-handoff manifest digest, canonical envelope bytes, reviewed producer-to-kind policy, and repository/head/workflow/run/attempt identity before it can construct an immutable typed index; admission then reconstructs each canonical artifact and verifies time/claim/artifact identity. The version-2 manifest now binds a separate producer-authenticated `ClaimEvidenceRequirement` containing the exact claim, independently required evidence kind, and `context` or `finding` publication authority. Raw current-head source lines are context only: they are withheld from finding-reference prompts and cannot publish a finding or `request_changes`; an explicitly producer-authorized source finding remains usable and retains exact path/line checks. Finding-free model `request_changes` and `blocked` verdicts cannot bypass receipt admission to publish a vacuous blocking review. Requirement/receipt kind mismatch, fixed-artifact semantic substitution, caller-supplied receipt dictionaries, model self-classification, stale identities, cross-kind receipts, marker-only sandbox output, noncanonical artifact bytes, and expired receipts fail closed before the GitHub publisher. This remains the owner prerequisite for ContextualWisdomLab/.github#1641 and issue #555. The reviewed `sandboxed_verify` adapter exists in owner source, but its actual central stdout/stderr/marker-to-manifest wiring and the trusted research producer are not yet integrated; exact-head hosted GREEN, immutable release, and the verified central consumer bump remain required.

## Unreleased
- Agent Runtime의 procedural evaluation envelope이 baseline/candidate/context/paired receipt identity뿐 아니라 deterministic `rejectionKey`, screening reason, `eligibleForApproval`까지 함께 해시한다. 동일 graph/evaluation evidence라도 durable rejection history에 따라 `validation_non_regression`과 `previously_rejected` 처분이 달라지면 envelope identity도 달라져, 하나의 signed evaluator handoff를 다른 approval-eligibility 결과에 재사용할 수 없다. 반환 evidence에도 rejection/disposition identity를 보존하며 `activationAuthorized:false`, Keyverse key custody, State / Checkpoint persistence, Policy / Approval, publication 경계는 그대로 유지한다. issue #584, PR #596.
- Agent Runtime이 separately authenticated evaluator handoff의 P-256 ECDSA 서명을 composition root가 공급한 trusted public key/key id로 검증하고 exact evaluation envelope digest와 최대 300초 validity window를 결합한다. 검증된 evidence도 signed expiry 이후에는 process-local authority로 재사용할 수 없으며 structural copy·wrong key/signer/envelope·expired/future handoff는 실패-폐쇄한다. Keyverse/owner가 signer identity·key custody를 유지하고 Noema는 private key·credential discovery·provider routing·durable State / Checkpoint·Policy / Approval·publication·activation authority를 소유하지 않는다. issue #584, PR #594.
- Agent Runtime의 screened procedural decision과 evaluator/profile 조건을 하나의 canonical envelope digest에 결합하고, separately authenticated evaluator handoff에서 받은 exact digest와 일치할 때만 process-local evaluation evidence로 admit한다. 이 adapter는 binding만 검증하며 digest 재계산 자체를 evaluator authentication으로 취급하지 않고, Keyverse/owner signer trust·credential·provider routing·durable State / Checkpoint·Policy / Approval·publication·activation authority를 소유하지 않는다. 모든 evidence는 `activationAuthorized:false`를 유지한다. issue #584, PR #593.
- Agent Runtime의 procedural candidate screening decision을 process-local provenance로 제한한다. `assessProceduralCandidate()`가 기존 lineage·held-out·safety·score 검증을 마친 결과만 locally admitted decision으로 발행하고, State / Checkpoint나 Policy / Approval 경계가 구조만 복사·직렬화·프록시·직접 생성한 lookalike를 `assertProceduralCandidateDecision()`으로 실패-폐쇄한다. 이 brand는 evaluator authentication·persistence·approval·activation authority가 아니며 모든 decision의 `activationAuthorized:false`는 유지된다. issue #584, PR #591.
Expand Down
14 changes: 12 additions & 2 deletions src/agent-runtime/procedural-evaluation-authority.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
/**
* Process-local evidence authority created only after a locally admitted screening decision is bound
* to an exact digest supplied through an already authenticated evaluator handoff. It carries opaque
* evaluator/profile identities for later State / Checkpoint retention without granting activation.
* evaluator/profile identities plus the exact rejection/disposition identity for later State /
* Checkpoint retention without granting activation.
*/
export interface ProceduralEvaluationEvidence {
readonly schemaVersion: "noema.procedural-evaluation-authority/v1";
Expand All @@ -32,6 +33,8 @@ export interface ProceduralEvaluationEvidence {
readonly contextDigest: string;
readonly baselineReceiptDigest: string;
readonly candidateReceiptDigest: string;
readonly rejectionKey: string;
readonly decisionReason: ProceduralCandidateDecision["reason"];
readonly envelopeDigest: string;
readonly eligibleForApproval: boolean;
readonly activationAuthorized: false;
Expand Down Expand Up @@ -93,6 +96,9 @@ async function evidenceDigest(
decision.contextDigest,
decision.baselineReceiptDigest,
decision.candidateReceiptDigest,
decision.rejectionKey,
decision.reason,
decision.eligibleForApproval,
metadata.evaluatorId,
metadata.evaluatorVersion,
metadata.policyVersion,
Expand All @@ -109,7 +115,9 @@ async function evidenceDigest(
* Computes the exact local envelope identity that a trusted evaluator producer can authenticate out
* of band. This digest is deliberately not an authentication result: callers must not treat knowing
* or recomputing it as evaluator identity, approval, persistence, publication, or activation authority.
* @param decision Locally admitted screening result whose exact graph and paired receipt identities bind the envelope.
* The identity includes deterministic rejection history and screening disposition so the same graph
* and receipt tuple cannot be authenticated once and replayed with a different approval eligibility.
* @param decision Locally admitted screening result whose exact graph, receipts, rejection key, and disposition bind the envelope.
* @param input Exact-key evaluator/profile metadata whose opaque digests identify the registered evaluation conditions.
* @returns Lowercase SHA-256 identity of the complete local procedural evaluation evidence envelope.
*/
Expand Down Expand Up @@ -152,6 +160,8 @@ export async function admitProceduralEvaluationEvidence(
contextDigest: decision.contextDigest,
baselineReceiptDigest: decision.baselineReceiptDigest,
candidateReceiptDigest: decision.candidateReceiptDigest,
rejectionKey: decision.rejectionKey,
decisionReason: decision.reason,
envelopeDigest: observed,
eligibleForApproval: decision.eligibleForApproval,
activationAuthorized: false as const,
Expand Down
29 changes: 27 additions & 2 deletions test/procedural-evaluation-authority.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {

const digest = (character) => character.repeat(64);

async function screenedDecision() {
async function screenedDecision(rejectedKeys = []) {
const baseline = await createProceduralGraph({
schemaVersion: "noema.procedural-graph/v1",
tenantId: "tenant-a",
Expand Down Expand Up @@ -71,7 +71,7 @@ async function screenedDecision() {
{ caseId: "case-2", score: 0.8, safetyViolations: 0 },
],
},
rejectedKeys: [],
rejectedKeys,
});
}

Expand Down Expand Up @@ -103,9 +103,34 @@ test("admits only an exact evaluation envelope digest from the trusted handoff",
assert.equal(admitted.candidateDigest, decision.candidateDigest);
assert.equal(admitted.baselineReceiptDigest, decision.baselineReceiptDigest);
assert.equal(admitted.candidateReceiptDigest, decision.candidateReceiptDigest);
assert.equal(admitted.rejectionKey, decision.rejectionKey);
assert.equal(admitted.decisionReason, decision.reason);
assert.equal(admitted.eligibleForApproval, decision.eligibleForApproval);
assert.equal(admitted.activationAuthorized, false);
});

test("binds rejection-history outcome into the authenticated evaluation envelope identity", async () => {
const eligible = await screenedDecision();
const rejected = await screenedDecision([eligible.rejectionKey]);
const input = authorityInput();

assert.equal(eligible.eligibleForApproval, true);
assert.equal(eligible.reason, "validation_non_regression");
assert.equal(rejected.eligibleForApproval, false);
assert.equal(rejected.reason, "previously_rejected");
assert.equal(rejected.rejectionKey, eligible.rejectionKey);
assert.equal(rejected.candidateDigest, eligible.candidateDigest);
assert.equal(rejected.candidateReceiptDigest, eligible.candidateReceiptDigest);

const eligibleDigest = await proceduralEvaluationEvidenceDigest(eligible, input);
const rejectedDigest = await proceduralEvaluationEvidenceDigest(rejected, input);
assert.notEqual(rejectedDigest, eligibleDigest);
await assert.rejects(
admitProceduralEvaluationEvidence(rejected, input, eligibleDigest),
/evaluation_evidence_digest_mismatch/,
);
});

test("rejects caller-created structural lookalikes", () => {
assert.throws(
() => assertProceduralEvaluationEvidence(Object.freeze({
Expand Down
Loading