Repository navigation
fix(gateway): operator read routes stop fabricating earnings and fleet data - #362
Merged
Merged
Conversation
…t data
GET /api/operator/earnings returned a new RANDOM daily earnings series on
every call, and /api/operator/machines, /certifications and /maintenance
returned hard-coded rows (a "Prusa MK4 Workshop" at 72% utilization, OSHA
certificates, nozzle maintenance). The public agent package advertises these
as an operator's earnings and machines (get_operator_earnings,
get_operator_machines, get_operator_certs), and /api/agent/me lists
"Earnings and what is owed", so an agent acting for an operator read invented
income. Product invariant 3: a missing fact is never a plausible number.
- The four routes now answer 501 {error:"not_available", message, see} and
point at the reads that are real: /api/agent/me (the operator's kernels,
devices and in-flight jobs) and /api/jobs/:jobId/execution (per-job payment
state from the escrow record). Earnings history cannot be served honestly
yet: escrow milestones carry no release time or per-operator payout record.
The real operator income read model is PX-7 (OperatorWorkDTO).
- GET /api/operator/policy/:kernelId returned DEFAULT_OPERATOR_POLICY when
the store read FAILED, so an operator's real guardrails could read as the
defaults. A failed read is now 503 read_failed; only "no saved row" means
the defaults (source: "default").
Tests (operator-no-fabrication.test.ts, 10): each route is 501 with no
fabricated fields; earnings are deterministic across calls; pointers present;
no-row policy = default; failed policy read = 503 and never the defaults; a
source ratchet forbids mock arrays and random VALUES in operator.ts (random id
suffixes stay allowed). Mutation-checked: re-adding random earnings -> 5 red;
restoring the policy fallback -> red. Full gateway suite 2991 passed, 6
skipped, 0 failed; tsc clean.
agent: pcc-readmodels (c255d7dc)
…eads are not available
The context pack table and the /api/agent/me route catalog described
/api/operator/{machines,earnings,certifications,maintenance} as returning an
operator's machines, "Earnings and what is owed", certifications and
maintenance windows. They now say "Not available yet (501)" and name the real
reads to use instead, so agents are not sent to routes that cannot answer.
agent: pcc-readmodels (c255d7dc)
LamaSu
added a commit
that referenced
this pull request
Sep 24, 2026
… fixtures pcc-economics (df42dbe5), from readmodels' server-side fabrication census (2026-09-24), which assigned this family to economics. - POST /api/swf/epochs/:epochId/distribute scored every participant with Math.random() (jobs, reputation, uptime, votes) and then DISTRIBUTED the epoch on those scores. It now refuses with 501 not_available until real per-epoch contribution inputs exist; the epoch is left untouched. - /api/rewards/*, /api/certificates* and /api/treasury/summary served fixtures: reward epochs and payouts, "claimed" claims with a fake tx hash, a mint that reported minted:true, and a 50000 USDC / 85000 USD treasury. All now answer 501 not_available with pointers to the real reads (escrow, jobs, capabilities, compliance), following PR #362's pattern. Paths stay registered so clients get an honest answer. - MCP pcc_depin_stats and the CLI depin command report each part on its own (pccFetchEach): an unavailable part is { unavailable, reason }, never zero and never a thrown whole. Tests: gateway economics-no-fabrication 10/10 (asserts no fixture value leaks and the refused distribution leaves the epoch unchanged); full gateway 2939 passed locally (the 2 capture files need @pcc/verifier built; CI builds it). mcp-server 71/71, tsc clean. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGRcaWqVay3ueHYRBcXJ7K
LamaSu
added a commit
that referenced
this pull request
Sep 29, 2026
…s, /api/issues or the demo API Shell's #3266 asked whether #380's pages read GET /api/operator/{machines, earnings,certifications,maintenance}, which still serve literals and Math.random money on master (routes/operator.ts; readmodels #362 / N32). They do not; this test makes that a checked fact rather than a code reading: it renders every dashboard tab, the machine page and every mobile tab against a stubbed gateway and fails if any request goes to those four routes, to the nonexistent /api/issues, or to /api/demo/*. Mutation-checked: adding one fetch of /api/operator/earnings to the dashboard turns it red. agent: pcc-operator-ux (f0734fab) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ever an empty list
GET /api/operator/approvals caught every storage failure and answered 200 with
{approvals: []}, an empty list that hides recorded approvals during an outage.
It now answers 503 read_failed (the same refusal shape the operator policy read
uses) with no approvals key. A real empty result is still a 200 with an empty list.
Reproduced first: the new M1 tests were run against a byte-identical copy of the
d30de64 route (operator.ts blob fbf9be8). With the store closed, and with a
throwing .all() for each of the four filter combinations, the route answered 200
and both tests failed with "expected 200 to be 503". With this change they pass.
Review: PR #362 cross-family review rm-n32-362-r1-d30de649, finding M1.
agent: implementer-delta for pcc-readmodels (c255d7dc)
…Type as unknown, never liquid-handler POST /api/operator/approvals persisted jobSummary.capabilityType as the invented "liquid-handler" whenever the caller left capabilityType out, and then served it back as recorded job truth. The field is optional in the spec (PendingApproval.jobSummary.capabilityType?: string) and job_summary is a JSON object, so an absent value can simply be absent: the key is now left out of jobSummary unless the caller sent one. A caller-supplied value, including an explicit "liquid-handler", is stored unchanged. No 400 is needed because the column holds the whole JSON object and does not require this key. Callers checked: the dashboard approvals tab reads a top-level capabilityType the gateway never sends, tool-suggestions.ts only advertises the endpoints, and neither relies on the default. scripts/ot2-agent.py keeps its own fallback for a missing key (it drives a single OT-2 liquid handler). Reproduced first: the new M2 test was run against a byte-identical copy of the d30de64 route (operator.ts blob fbf9be8). A POST with only kernelId and agentId stored and returned capabilityType "liquid-handler", and the test failed with "expected { ...(2) } to not have property capabilityType" (Received "liquid-handler"). With this change it passes. Review: PR #362 cross-family review rm-n32-362-r1-d30de649, finding M2. agent: implementer-delta for pcc-readmodels (c255d7dc)
…update changed nothing
The approve and reject routes restrict their update to status "pending", but they
ignored its changed-row count and then accepted any row that read back as
"approved" (or "rejected") as success. A second approve of an approved record, a
second reject of a rejected one, or an approve of an auto-approved one all
answered 200 {approved: true}, as if this request had made the decision. The
mixed cases (reject after approve, approve after reject) answered 404 with the
ambiguous "not found or already decided".
Both routes now use the update's changed-row count:
- the record does not exist: the existing 404 answer, unchanged;
- 0 rows changed and the record exists: 409 {error: "already_decided", status,
message} carrying the record's current status, with no approved/rejected flag,
and the record is left as it was (decidedAt and rejectionReason untouched);
- 1 row changed: success as before.
Reproduced first: the new M3 tests were run against a byte-identical copy of the
d30de64 route (operator.ts blob fbf9be8). Approve twice and reject twice both
answered 200 for the second call (expected 409), reject-after-approve and
approve-after-reject answered 404 (expected 409), and approve of an auto-approved
record answered 200 (expected 409). With this change all pass, and the unknown-id
control still answers the existing 404.
Dashboard left untouched: OperatorDashboardPage handleApprovalAction does not
check res.ok, so it treats this 409 (like any non-2xx) as a success. Flagged for a
separate change.
Review: PR #362 cross-family review rm-n32-362-r1-d30de649, finding M3.
agent: implementer-delta for pcc-readmodels (c255d7dc)
… and the 404 says only "not found" (N32) Follow-ups to the r1 MEDIUM fixes, found by implementer-delta while fixing them: - A repeated ?status= or ?kernelId= arrives as a list, and the SQLite bind threw. Before M1 that was a 200 with an empty list; after M1 it was a 503 read_failed, a client error answered as an outage. It is now 400 invalid_query, with a test. - After M3, an already-decided approval gets 409 already_decided. So the 404 on approve or reject is reachable only for a missing record. Its text, "Approval not found or already decided", is now "Approval not found". agent: pcc-readmodels (c255d7dc)
… 501, or no route (N32) Found while packing #362 round 2. Round 1 could not see whether the agent package's get_operator_* tools handle the 501 refusals honestly. They did not: the public manifest (apps/dashboard/public/ agent-package.json) and agent-package-test.json still promised "utilization and uptime stats", "daily earnings and cumulative totals" and "OSHA, safety training, PCC network certs". Those routes answer 501 not_available. get_operator_dashboard points at GET /api/operator, which no route registers. The four descriptions now say exactly that, and where the real data is: - /api/agent/me for kernels, devices and in-flight jobs; - /api/jobs/:jobId/execution for per-job payment state. The certifications text says that certifications typed at registration are the operator's own claim and are not checked. Only these four description lines changed in each file. The five gateway tests that read the manifests pass. agent: pcc-readmodels (c255d7dc)
LamaSu
added a commit
that referenced
this pull request
Oct 3, 2026
Merges PR #362 (fix(gateway): operator reads stop fabricating earnings and fleet data) into #389's branch (feat/readmodels-operator-work). Conflict: packages/gateway/src/routes/agent-introspection.ts - #389 (HEAD) inserted two new AGENT_OPERATIONS entries, `operator.work` and `operator.income`, right after the `operator.maintenance` entry and before `operator.emergencyStop`. - #362 rewrote the `summary` text of the four existing entries (operator.machines, operator.earnings, operator.certifications, operator.maintenance) to say "Not available yet (501)" plus where the real data now lives, since those routes have no backing store and were answering fabricated data. - The insertion point for #389's two new entries sits immediately after one of the four lines #362 rewrote (operator.maintenance), which is why git couldn't auto-merge this hunk. - Resolution: kept all six entries — #362's four rewritten "Not available yet (501)" summaries unchanged, followed by #389's two new operator.work / operator.income entries unchanged. No overlap in meaning: #362 is marking old fabricated endpoints as gone, #389 is adding new real ones. Conflict: packages/gateway/src/routes/context-pack.ts - Same shape: #389 inserted two new "Operator Management" table rows (/api/operator/work, /api/operator/income) right after the /api/operator/maintenance row and before /api/operator/approvals. - #362 rewrote the same four existing rows' descriptions to "Not available yet (501): ...". - Resolution: kept #362's four rewritten rows followed by #389's two new rows, same reasoning as above. packages/gateway/src/routes/operator.ts, agent-package-test.json and apps/dashboard/public/agent-package.json merged automatically (no conflict markers) and were left as git resolved them. agent: pcc-readmodels (c255d7dc)
LamaSu
marked this pull request as ready for review
October 3, 2026 22:12
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.
The production gateway served fabricated operator data.
GET /api/operator/earningsreturned a new random daily earnings series on every call./api/operator/machines,/certificationsand/maintenancereturned hard-coded rows (a "Prusa MK4 Workshop" at 72% utilization, OSHA certificates, a nozzle replacement).The public agent package advertises these routes as an operator's earnings and machines (
get_operator_earnings,get_operator_machines,get_operator_certs), and/api/agent/melists them as "Earnings and what is owed". So an agent acting for an operator read invented income. This violates product invariant 3: never substitute plausible values for missing real data.Change (branch
fix/operator-routes-no-fabrication, base masterac86a404)501 {error:"not_available", message, see}and point to the reads that are real:/api/agent/me(the operator's actual kernels, devices and in-flight jobs) and/api/jobs/:jobId/execution(per-job payment state from the escrow record; PR feat: JobExecutionDTO read model; job detail stops reading mock data (PX-6) #353).GET /api/operator/policy/:kernelIdreturnedDEFAULT_OPERATOR_POLICYwhen the store read failed, so an operator's real guardrails could read as the defaults. A failed read is now503 read_failed. Only "no saved row" means the defaults (source: "default")./api/agent/meroute catalog now say "Not available yet (501)" and name the real reads.Tests
The new file
operator-no-fabrication.test.tshas 10 tests:operator.ts(random id suffixes stay allowed).Results:
tsc --noEmitis clean.Follow-ups (not in this PR)
swf.tsepoch distribution uses random contribution scores;spaces.ts,marketplace.ts,rewards.ts,logistics.ts,orchestrator.ts,protocols.tsandagents.tshold mock arrays). A per-route census is in progress.🤖 Generated with Claude Code
https://claude.ai/code/session_01A2ZvsqsAb7jC7AC8Viisqn