fix(gateway): credit composition reputation at completion, not submission-ack (corrects #96) - #208
Open
LamaSu wants to merge 6 commits into
Open
fix(gateway): credit composition reputation at completion, not submission-ack (corrects #96)#208LamaSu wants to merge 6 commits into
LamaSu wants to merge 6 commits into
Conversation
… + 2PC ledger implementer-r3: clean-room TS port of the PyLabRobot poka-yoke pattern (pattern reference only, no dependency on pylabrobot). Offline, in-memory, zero external deps: CapacityModel refuses out-of-bounds requests independent of availability; ResourceTracker adds reserve()/commit()/ rollback() two-phase-commit semantics on top; five typed refuse-to-start error classes carry structured details instead of string messages. This is the resource layer for PCC's new preflight() gate (R3, ROBOTICS-BUILD-SPEC.md); wired into MachineAdapter/job-handler in follow-up commits. 19/19 unit tests green, typecheck clean.
…eference impl implementer-r3: adds preflight(input): Promise<PreflightResult> as an OPTIONAL method on MachineAdapter (packages/kernel/src/adapters/types.ts) so none of the ~10 existing adapters (hamilton, ipp, octoprint, opcua, sila, etc.) are affected — confirmed via full suite run, 803/803 tests green, zero regressions. MockFDMAdapter gets the reference implementation: a calibrated filament/build-volume capacity model backed by @pcc/resource-tracker's 2-phase reserve/commit/rollback ledger, plus commitReservation()/ rollbackReservation() to finalize a preflight-approved hold. Acceptance demonstrated in src/adapters/__tests__/mock-fdm.test.ts: an under-resourced job (200g requested, 50g on hand) is refused with a typed insufficient_resource error before execute() is ever reachable; a sufficient job passes and returns a reservation token; commit permanently consumes capacity, rollback releases it.
…dler implementer-r3: preflight runs AFTER inbound auth verification (~L175) and BEFORE the session key is minted / execute() is invoked (~L220) — the exact PyLabRobot poka-yoke seam (tracker check before backend.aspirate), applied to the third-party kernel-builder SDK path. Kept kernel-sdk decoupled from @pcc/kernel and @pcc/resource-tracker on purpose: kernel-sdk is published (private:false) and must not gain a workspace dependency on either private package. `preflight` is a structurally-typed optional callback (PreflightCheckResult), symmetric to the existing `execute` callback — builders backed by a MachineAdapter map its preflight() result into this shape when wiring a handler. Refusal throws the new typed KernelPreflightError. 5/5 tests green (src/__tests__/job-handler.test.ts): under-resourced job refused before execute() is ever called; typed reason/details on the thrown error; sufficient job passes through and returns a normal response; no-preflight kernels are unaffected (backward compatible); preflight confirmed to run after — never before — auth verification.
…credit Additive enum member (type union + Zod schema) so real-execution mode can record a step outcome at ack without applying a reputation delta. finalizeReputation already only settles success/failed, so pending is naturally skipped and the composition bonus is withheld while any step is still pending. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L95pzhZjDxuUFiZtJT62mY
…ion, not ack
Fixes the reputation-at-completion bug: real-execution mode credited each
step's reputation off the submission ack, and physical completion never
corrected it (ack-then-fail kept its +10).
- compose.ts executeComposition: gate on PCC_COMPOSE_EXECUTE_REAL. In real
mode, ack-success records a "pending" outcome (no finalize, no
short-circuit) and ack-throw records "failed" + finalizes that step;
NOOP/test mode is unchanged (still finalizes at loop end). Thread
{compositionId, stepIndex} onto the job via parameters.__pccStep
(createProductionBinding + ExecutorBinding.runStep ctx arg) — no migration.
- reputation.ts: new settleStepReputationForJob(jobId, verdict, opts) seam —
reads the job's __pccStep bridge, flips the pending outcome, and reuses
finalizeReputation to apply +10/-15 and the one-time +5 bonus. Inert for
non-composition jobs; idempotent.
- kernel-service.ts: call the seam at all local completion points (success
after processEvidence, failure else-branch, .catch) on both the Sentry and
fallback paths, best-effort. Secondary: wire MachineAdapter.preflight() into
submitJob, gated on parameters.requirements (typed PreflightRefusedError).
- paid-job-flow.ts: call the seam on external verified success and before the
422 oracle_verification_failed, best-effort.
- job.facade.ts: thread parameters.requirements into submitJob.
Additive: NOOP/test mode keeps ack-finalize, so every existing test stays
green. New tests: real-mode deferral (all pending, no deltas), credit at
completion (+10 then +5 bonus once, idempotent), ack-then-fail (-15, no +10),
single-job no-op, preflight refusal.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L95pzhZjDxuUFiZtJT62mY
Close caveat #1 on PR #208: the two settleStepReputationForJob call-sites on the EXTERNAL completion path (PUT /api/jobs/:jobId/complete in paid-job-flow.ts) were type-checked and seam-unit-tested but had no runtime test, because the original paid-job-flow.test.ts is in the vitest exclude (stale post-facade-rewrite expectations). This adds a NEW focused file that drives the real /complete route via app.inject and spies on settleStepReputationForJob to assert: (a) oracle-verified success -> settle(jobId, "success", {evidenceBundleId}), 200 (b) 422 oracle_verification_failed -> settle(jobId, "failed") (c) inert for a job with no __pccStep bridge (still 200) Only the real-I/O boundaries are mocked (evidence storage + verification oracle); the reputation module is spied but delegates to the real impl, so the +10/+5 credit and -15 debit are asserted end-to-end against the in-memory ledger. No escrow-client/on-chain mocks needed: MOCK_SETTLEMENT=true + a seeded job with no negotiation session gates every V2/V3 branch off. The original excluded file is left excluded (its scope-revocation + response-code expectations remain stale). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L95pzhZjDxuUFiZtJT62mY
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
PR #96 wired composition execution to reputation but credits deltas at submission-ack: in
executeComposition,recordStepOutcome+finalizeReputationfire per step right aftereffectiveRunStep— which only submits the job (physical completion is async viaprocessEvidence). Completion never corrects it (reputationrefs insettlement-service.ts/kernel-service.ts= 0). Net: a step that acks then physically fails later keeps its +10 — optimistic credit, no compensation.Fix (real-mode-only,
PCC_COMPOSE_EXECUTE_REAL=true)pendingstatus (zero delta at ack).settleStepReputationForJob(jobId, verdict), wired into the four completion decision points:kernel-service.tslocal success / failure / catch, andpaid-job-flow.tsexternalPUT /completesuccess +422 oracle_verification_failed. Success → +10 (+one-time +5 composition bonus); fail → nothing (deferral ⇒ no optimistic credit to reverse; compensation is free).finalizeReputationunchanged → idempotency + composition bonus preserved.parametersJSON (__pccStep) — no schema migration.Secondary
Wires the dormant
MachineAdapter.preflight()intoKernelService.submitJob, requirements-gated (only fires when a job declaresparameters.requirements) — additive; ships the@pcc/resource-tracker2-phase reserve/commit/rollback package it calls.Tests
preflight()refusal (typed, spy-confirmed) + requirements-gated. Kernel suite 803/0. NOOP/test path untouched → existing ack-time reputation tests stay green.Caveats (settlement code — flagged honestly)
settleStepReputationForJobcalls inpaid-job-flow.tsare type-checked + the seam is unit-tested, butpaid-job-flow.test.tsis in a pre-existing vitest exclude, so those two call-sites have no live test. Local path fully tested.pending(documented; sweep out of scope).v2-settlement-wiring,gateway-evidence-v2LIT label) are env-dependent (Story/IPFS/chain unavailable) and verified to predate this branch — not introduced here.🤖 Generated with Claude Code