# Audit report

> Review the changed-source correction at this exact commit relative to eb6f568c6b70682b6555f17128beb1d028f10150 (runtime) and 41683cf02aa2039b14a99f179496a4ca9c91e477 (signal kernel). Original independent jobs 4bfe5f40-24e0-4429-b930-397b3927ff5e and 5184c276-7378-44a6-9d86-a25e7916ccf7 remain intact. This is a focused follow-up of material fixes, not a replacement purchase; do not spawn paid jobs. Inspect git diff and confirm fixes: constructor EOA bypass prevented by msg.sender==tx.origin plus existing grant and code checks; pre-registration digest revocation survives later register, consumed nonce cancellation rejects; canonical finalized factory store recovery leaves old unknown tx untouched; proposals and execution reject untracked canonical stores; exact reviewed factory/store bytecode now checked before registration; current/withdrawn/superseded feed status and finalized grants; vault/signal consumer claim exclusion; definitive wrong hash rejected before attachment, historical mismatch terminal; byte calldata normalized; separately bounded revocation rows and operations. Reference scheduler bases uncertain-effect retention on durable signed tx row, ends unsigned denied/expired runs, original unknown broadcasts remain; provider bad order cannot abort later orders and attempts bounded; stale sequence pre-acceptance resets only unsigned request; run deadline vs observation/publication expiry split. Existing transaction engine now releases an expired unknown only with exact competing sender+nonce transaction and receipt in canonical finalized history, never merely nonce or missing original receipt, never sends a cancellation. Review end-to-end permission, gas/accounting, finality and race boundaries; no funds/custody/inference/launch expansion. PulseModule byte-identical: publisher-attested public chain data not a truth oracle, one canonical feed per project. Direct noncanonical downstream stores never receive approval. Scope remains finite owner grants; upgraded evaluator only via genuine Claus decision. Contract/UI observations are immutable history, status does not erase records. Agent patch hashing helpers exist in exact deployed baseline and tsc + real consumer fixture validation passed; no helper substitution. Read new regression tests; existing relevant invariants retain success. Return reproducible findings and distinguish limited-scope design from defects. Do not edit source, publish messages, deploy, sign, or spend. Also verify the public contract-request-client accepts chain 1 and 11155111 only, exact chain-bound pins and identical finality; its real local mainnet-chain prepare/receipt recovery test passed.

| | |
|---|---|
| Repository | https://github.com/contractclaus/claus-network |
| Commit | `8b03d350c768cd4ef6f36107aca22e1e09729935` |
| Job | `d3229638-a2a2-4dd7-97f0-8e2552c31e96` |
| Judged | 2026-10-13 01:46 UTC |
| Findings | 1 medium · 5 low · 4 info |

Four agents audited the code as it is at `8b03d35`, each in one area (math, permissions, economics, control flow),
and a judge reproduced, merged and ranked what they found, then read the code once more itself. Nothing in the repository was changed or deployed.

## Findings

### 1. Medium: Public capabilities discovery and /health now fail on any signal-chain RPC or evidence error because admission() performs a live bytecode check that discovery() does not catch

`src/signals/index.mjs:150`

```
      catch (error) { if (!['signal_not_enabled','signal_admission_changed','signal_scope_not_reviewed'].includes(error.code)) throw error;
```

This correction added `await chain.validateAdmission(config, await artifacts.get(...))` to admission() (src/signals/index.mjs:37). admission() is also what discovery(chainId) calls, and discovery() still only maps the three pre-existing storage-only codes to admission:'not_enabled'; everything else is rethrown. validateAdmission() (adapters/signal-read.mjs:77-82) now issues getChainId, getCode(factory), factory.executor() and a second getCode on every call and can throw `wrong_chain`, `signal_factory_changed`, `signal_evidence_invalid`, `signal_chain_unavailable` (createSignalRouter when no client is configured for the admitted chain) or an uncoded viem transport error; artifacts.get() can throw `not_found`. runtime/server.mjs:45 wires discovery for chains 11155111 and 1 into access.discovery(), which backs the unauthenticated `capabilities` service call (interfaces/service.mjs:5, GET /v1/public/capabilities) and GET /health (api/gateway.mjs:44, whose catch answers 503 service_unavailable). Before this commit admission() did no network I/O (git show eb6f568:src/signals/index.mjs), so these public reads could not fail this way. Consequences: (1) a transient public-RPC failure (retryCount 0, 15 s timeout) or a stale factory pin takes down the whole capabilities document and /health for every unrelated feature; (2) every unauthenticated capabilities or health request now performs four uncached RPC round-trips per enabled signal chain, which turns load on a public route into RPC load. The fail-closed behaviour of writeAllowed()/consumer() is correct and unaffected; the defect is confined to the public discovery path. Minimal fix: in discovery() also catch the chain-evidence codes (signal_chain_unavailable, wrong_chain, signal_factory_changed, signal_evidence_invalid, not_found) and code-less transport errors and return a degraded state such as {admission:'unavailable', reason}, or keep validateAdmission out of the discovery path and leave it in writeAllowed()/consumer() where fail-closed is required. Merged from audit_permissions and audit_flow, which reported the same mechanism.

**Reproduction**

State: network-capabilities 'signal:1' enabled with an accepted assessment and a reviewed review case (the rows test/signals.test.mjs fixture() writes). Inject a chain port whose validateAdmission rejects with (a) code signal_chain_unavailable, (b) code signal_factory_changed, (c) code signal_evidence_invalid, (d) a viem HttpRequestError without a code. Call signals.discovery(1). Expected (behaviour at eb6f568): resolves with a not_enabled/unavailable record so capabilities and /health keep answering. Actual, verified with a scratch Node test against this tree (createSignals with those four validateAdmission stubs): discovery(1) rejects with exactly the injected error in all four cases, while a control stub throwing signal_scope_not_reviewed still resolves to admission:'not_enabled'. Through runtime/server.mjs:45 and src/access/index.mjs:93 the rejection propagates to the public capabilities call and GET /health.

### 2. Low: Reference scheduler pins the lane on any chain-transactions row, including cancelled, reverted, confirmed or never-broadcast prepared rows, so a persistent pre-reconcile error blocks every later slot

`reference/pulse-scheduler.mjs:48`

```
   const signed=run.write?.operation&&await store.read('chain-transactions','signal-record:'+run.write.operation.id);
```

The fix intends that only a durable signed transaction with an uncertain effect may own the lane indefinitely (comment on lines 46-47). The implementation tests only whether a row exists; `signed` is truthy for rows in state 'prepared' (signed, never broadcast), 'cancelled' (submit() denied authorization before any broadcast and already released the slot and budget), 'confirmed' and 'reverted'. None of those has an uncertain effect, yet their presence disables both the run.expiresAt cutoff and the terminal error list. If publisher.step() then throws before it can return its own outcome, the run is retried every 60 s forever: schedule() sees the active job in state 'waiting' and never computes a new slot. Persistent pre-reconcile throws exist in adapters/signal-publisher.mjs: `signal_operation_mismatch` at line 48/52 once the operator admits a budget with a different grantId (an authority bump kills every old grant by design and config.budget is independent of config.schedule, so reference-pulse-schedules' configHash still matches), and `transaction_not_found` from transactions.status() at adapters/sepolia-transactions.mjs:196 after the publisher signer is rotated. With such a row the old run can only be ended by editing the database. Liveness defect of the reference loop only; no funds, grants or on-chain state are affected. Fix: treat only uncertain rows as retaining, e.g. `const row=...; const signed=row&&['broadcasting','submitted','unknown'].includes(row.value.state)` (confirmed/reverted/cancelled rows are already resolved by the normal try path on the next tick). Merged from audit_economics and audit_flow.

**Reproduction**

Scratch Node test with openStore(':memory:'), createJobs and createPulseScheduler as in test/pulse-reference.test.mjs. config={projectId:'lane',startsAt:t0,expiresAt:t0+14400000,intervalMs:3600000,maximumRuns:4}. pipeline.step sets run.write={operation:{id:'op-'+run.id}}, checkpoints, writes chain-transactions 'signal-record:op-lane:0' = {state:'cancelled'} once (what submit() leaves after an authorization denial), then throws {code:'signal_operation_mismatch'} on every call. tick() #1 at t0 -> {state:'unavailable', runId:'lane:0'}. Advance now by 7200001 ms (past run.expiresAt = t0+3600000) and tick() #2, then +3600000 ms and tick() #3. Expected: run 'lane:0' ends 'failed' and slot 'lane:2'/'lane:3' is scheduled. Actual: both ticks return runId 'lane:0' state 'unavailable', workflow-jobs count stays 1, and no later slot is ever created. The same result was verified for row states 'confirmed', 'reverted' and 'prepared'. Control in the same scratch test: with no row, the identical error ends the run 'failed' and the next tick schedules 'lane:1'. The existing test 'an unsigned failed intent releases the next scheduler slot but an unknown signed transaction retains it' only covers state 'unknown'.

### 3. Low: The consumer-profile claim is taken by the first prepare attempt and never released, so an unsigned expired signal_store_prepare (or a failed vault attempt) permanently closes the other profile with a

`src/signals/index.mjs:87`

```
        if(!claim)await tx.write('project-consumer-claims',projectId,{profile:'claus-signal-v1'},0);
```

The vault/signal mutual exclusion itself is documented design ('A project can select one consumer profile, signal or vault') and the race it closes is handled correctly: both writers read and write project-consumer-claims inside the same optimistic transaction. The defect is when the selection becomes binding. The signal claim is written inside prepare() for every kind==='store' operation, including the ordinary unsigned path whose only result is an intent the owner may never sign (state 'prepared', expiresAt now+24h). The vault side writes its claim when a deployment attempt is queued (src/vaults/index.mjs:42). No code path deletes or overrides a project-consumer-claims row (grep: only these two writers), and the signal path additionally rejects on the mere presence of any project-vaults row regardless of state (line 84), whereas vaults.create itself treats 'cancelled'/'blocked'/'failed' attempts as retryable. So an owner who prepares a store transaction once to inspect it and lets it expire can never deploy a native vault for that project, and a vault attempt that ended 'failed' before any contract existed can never be followed by a signal store, although in both cases nothing exists on chain and no verified consumer was recorded. Both refusals use the code `signal_review_context_changed`, which describes a reviewed-context change rather than a profile conflict. Fix options that keep the agreed exclusivity: write the signal claim only when a store becomes 'verified' (confirmed receipt or recovery) and ignore terminal non-verified project-vaults rows; or expose an owner call that releases an unused claim once the operation has expired; and use a dedicated code such as consumer_profile_conflict. Merged from audit_permissions and audit_flow.

**Reproduction**

Scratch Node test using createApplication from runtime/server.mjs with chainPorts.signal stubbed (validateAdmission/validateStoreBuild/assertTrackedStore no-ops, prepareStore returning a transaction) and the vault fixture rows from test/vaults.test.mjs (network-capabilities 'vaults', review-pilots, a verified claus-readonly-v1 chain binding on 11155111, cap.policies[p]) plus an enabled, reviewed 'signal:11155111' admission whose factory artifact is stored through app.artifacts.put. Case A: owner calls signals.storePrepare(p,{idempotencyKey:'look'}) -> state 'prepared', no transactionHash, no project-signals row; advance the clock 2 days (operation expired), re-authenticate, call vaults.create(owner,p). Expected: a vault job is queued (nothing exists on chain). Actual: rejects with code signal_review_context_changed and project-consumer-claims/p is still {profile:'claus-signal-v1'}. Case B (mirror): vaults.create -> 'queued'; set project-vaults/p state to 'failed'; signals.storePrepare(owner,p,{idempotencyKey:'store'}) rejects with signal_review_context_changed, while vaults.create(owner,p) immediately afterwards queues a new attempt.

### 4. Low: grantRegister prepares the registration transaction for a grant ID the owner has already revoked through the API, so the publisher pays gas to register a dead grant and the nonce is consumed

`src/signals/index.mjs:178`

```
      const prepared = await chain.registerGrant(await signal(projectId), current.binding, row.value, signature);
```

The contract deliberately lets the owner revoke a signed grant ID before registration and registerGrant still succeeds for that ID (contracts/src/ClausSignalStore.sol:117-123 and :94-107, covered by testOwnerCanRevokeSignedGrantBeforeAndAfterRegistration). The API mirrors neither state when preparing the registration call: grantRegister checks scope, operation kind, signature shape, expiry and the owner signature, but does not consult `signal-revocations` (which revoke() writes for any 32-byte ID at line 203) nor the on-chain grant(id).revoked flag that chain.grant() now returns for unregistered IDs. recordPrepare, by contrast, does reject `signal_api_revoked` (lines 95 and 217). The registration transaction is therefore handed to the publisher, succeeds on chain, consumes the owner's grant nonce permanently (cancelGrantNonce then reverts InvalidGrant), emits PublisherGranted, and every later publish reverts GrantInactive while recordPrepare rejects signal_api_revoked. No authority is gained, so this is a gas/UX and nonce-hygiene gap rather than a security defect. Fix: in grantRegister requireThat no signal-revocations row exists for row.value.grantId and that chain.grant(signal, grantId) is not revoked, rejecting with signal_api_revoked / signal_grant_inactive before any transaction is prepared.

**Reproduction**

Scratch Node test with createSignals and the test/signals.test.mjs fixture rows, chain.grant stubbed to return {terms:{publisher:0x0},revoked:true} (what adapters/signal-read.mjs:72 now returns for a revoked unregistered ID). Steps: storePrepare + submit (confirmed) -> grantPrepare(owner,p,{publisher,maximumWrites:24,minimumInterval:3600,expiresAt:now+86400,nonce:'88',idempotencyKey:'grant'}) returns operation G with grantId D, state awaiting_signature -> revoke(owner,p,{grantId:D,idempotencyKey:'revoke'}) returns 'prepared' and grant(owner,p,D).apiRevoked === true -> grantRegister(owner,p,{operationId:G.id,signature:<65 bytes>}). Expected: rejected before a transaction is prepared. Actual: returns state 'prepared' with a registerGrant transaction and chain.registerGrant was invoked once. On chain, contracts/test/scratch/Boundaries.t.sol testPreRevokedRegistrationConsumesNonceAndEmits shows that broadcasting it succeeds, sets grantNonces[owner][88]=true, emits PublisherGranted and makes cancelGrantNonce(88) revert InvalidGrant.

### 5. Low: Reference pipeline never bounds validUntil by the grant's expiresAt, so every slot in the final maximumAge window before grant expiry fails terminally with signal_data_expired

`reference/pulse-pipeline.mjs:67`

```
    observedAt:consumed.observation.observedAt,validUntil:Math.min(consumed.observation.observedAt+run.rule.maximumAge,
```

The store requires validUntil <= grant.expiresAt (contracts/src/ClausSignalStore.sol:141) and prepareRecord mirrors it as `input.validUntil <= Number(g.terms.expiresAt)` with error signal_data_expired (adapters/signal-read.mjs:157). The pipeline clamps validUntil to observedAt+maximumAge and to floor(serviceExpiresAt/1000) only (lines 67-68, changed by this commit to drop the run deadline); the grant's expiresAt is a third, independent bound that runtime/pulse-host.mjs never ties to config.schedule.expiresAt (it only checks budget.grantId === config.grantId). Once observedAt + maximumAge exceeds grant.expiresAt, signal_record_prepare throws signal_data_expired, which reference/pulse-scheduler.mjs:53 now lists as terminal, so the slot ends 'failed'; the next slot fails identically until the grant expires and the error becomes signal_grant_inactive. A shorter validUntil equal to grant.expiresAt would have been accepted by both the contract and the API (and by the pipeline's own 900 s margin for any slot more than 15 minutes before expiry), so the reference silently loses up to one maximumAge of publication capacity per grant although maximumWrites is not exhausted; the docs' advice for signal_data_expired ('Obtain a fresh observation') does not help here. Reference-scope gap, not a contract or API defect. Fix: read the grant (signal_grant) and include Number(terms.expiresAt) in the Math.min, or have pulse-host bound config.schedule.expiresAt by grant.expiresAt*1000.

**Reproduction**

Scratch Node test wiring createPulsePipeline's signal_record_prepare to the real adapters/signal-read.mjs prepareRecord over a mock viem client (projects/pendingProjectOwners/version/grant/latest readContract stubs, getBlock timestamp head=1800000000, call resolving). Rule maximumAge=7200, grant.expiresAt=head+3600, run.serviceExpiresAt=head*1000+86400000, observation observedAt=head. pipeline.step(run) rejects with code signal_data_expired; the prepared body carried validUntil = head+7200 > grant.expiresAt. Calling prepareRecord with the same body but validUntil = grant.expiresAt (head+3600) returns a signable transaction. With createPulseScheduler, signal_data_expired is in the terminal list at reference/pulse-scheduler.mjs:53, so the run ends 'failed' and the next hourly slot repeats the failure.

### 6. Low: prepareRecord accepts and simulates validUntil values inside the next slot (head.timestamp+1 .. head.timestamp+12) that are guaranteed to revert InvalidRecord when mined, charging the publisher for a

`adapters/signal-read.mjs:156`

```
      requireThat(input.validUntil > Number(head.timestamp) && input.observedAt <= Number(head.timestamp) &&
```

The API-side guard compares validUntil with the latest block's timestamp and the subsequent eth_call at line 165 executes in that same latest-block context, so both pass for validUntil = head.timestamp + 1. The store (contracts/src/ClausSignalStore.sol:141) requires `validUntil > block.timestamp` of the including block, which on mainnet and Sepolia is at least one slot (12 s) later than head. Any validUntil in (head.timestamp, head.timestamp+12] therefore passes preparation and simulation but cannot execute. src/signals/index.mjs:213 only requires validUntil >= floor(now/1000)+1. For the hosted reference publisher the reverted transaction is still settled as spent against the finite budget (adapters/signal-publisher.mjs finalize) and the operation ends signal_transaction_reverted; the reference pipeline avoids the window with its own 900 s margin, so this affects direct API users of signal_record_prepare. The same single-block lag applies to the ClausPulseModule staleness check, which prepareRecord does not pre-check. No data is written and no authority is affected. Fix: require validUntil > head.timestamp + a slot margin (e.g. 60 s) in prepareRecord and in recordPrepare's integer bound.

**Reproduction**

Scratch Node test: createSignalRead({client,chainId:1}) with a mock client whose latest block has timestamp 1800000000 and whose call() resolves. prepareRecord(signal,binding,{grantId,sequence:1,expectedVersion:1,observedAt:1800000000,validUntil:1800000001,sourceHash,data}) returns a signable transaction and performed exactly one eth_call. Foundry (contracts/test/scratch/Boundaries.t.sol testValidUntilOneSecondAfterHeadRevertsInNextBlock): publish(grantId,1,1,head,head+1,...) succeeds when executed at block.timestamp == head (the simulation context) and reverts InvalidRecord at block.timestamp == head+12 (the earliest mainnet inclusion). Expected: a record the API prepares and simulates can execute; actual: deterministic revert and spent gas.

### 7. Info: grantPrepare's upper bound equals the contract maximum exactly, so a grant prepared at the API cap reverts InvalidGrant if the registering block's timestamp is below the API host's wall-clock second

`src/signals/index.mjs:167`

```
      integer(input.expiresAt,Math.floor(now()/1000)+60,Math.floor(now()/1000)+30*86400);
```

The API accepts expiresAt up to floor(now/1000)+30*86400 inclusive against the API host's wall clock, while ClausSignalStore.registerGrant (contracts/src/ClausSignalStore.sol:102) rejects `g.expiresAt > block.timestamp + MAX_GRANT_DURATION` against the including block's timestamp. Block timestamps are slot starts and trail the wall clock by up to ~12 s, and the API host's clock may be ahead of chain time, so a grant prepared at the cap is valid for the API and for the owner's EIP-712 signature but reverts if it is included in a block whose timestamp is below the second at which the API computed the cap. registerGrant in adapters/signal-read.mjs builds the intent without simulating it, so nothing catches this before broadcast. In practice preparation, owner signature and broadcast rarely complete within one slot, so the window is narrow; the signed terms register normally once block.timestamp reaches the API's second. Numeric seam between two clocks, no funds or authority affected. Fix: cap the API maximum at floor(now/1000)+30*86400 minus a slot margin (e.g. 60 s), or simulate registerGrant before returning the intent.

**Reproduction**

API wall clock 1800000005 s: grantPrepare accepts expiresAt = 1800000005+2592000 = 1802592005 (equal to the maximum). Foundry (contracts/test/scratch/Boundaries.t.sol testGrantAtApiCapRevertsWhenBlockTimestampLagsWallClock): with block.timestamp = 1800000000, registerGrant(Grant(publisher,1,2,60,1802592005,7), ownerSignature) reverts InvalidGrant because 1802592005 > 1800000000+2592000; after vm.warp(1800000005) the same grant and signature register successfully.

### 8. Info: Design limit, documented: registering a grant whose digest was revoked before registration still consumes the owner nonce and emits PublisherGranted for a grant that can never publish

`contracts/src/ClausSignalStore.sol:106`

```
        emit PublisherGranted(id, g.publisher, generation, g.maximumWrites, g.expiresAt);
```

revokeGrant(id) before registration sets grants[id].revoked = true with terms.publisher == 0, so registerGrant() does not take the early return at line 97, runs the full validation, marks grantNonces[owner][nonce] = true, stores the terms and emits PublisherGranted even though publish() will always revert GrantInactive. The security outcome is correct and documented in docs/signals.md ('registering it later cannot undo the revocation'), and it is covered by testOwnerCanRevokeSignedGrantBeforeAndAfterRegistration. Two side effects are worth an explicit decision: an indexer that treats PublisherGranted as 'active grant' without also reading grant(id).revoked shows a live publisher, and the owner can no longer cancelGrantNonce(nonce) for that nonce. Recorded as limited-scope design rather than a defect; a one-line guard (`if (grants[id].revoked) revert InvalidGrant();` or skipping the event) would make the event authoritative, at the cost of a small bytecode change to an already reviewed contract.

**Reproduction**

Foundry (contracts/test/scratch/Boundaries.t.sol testPreRevokedRegistrationConsumesNonceAndEmits): g = Grant(publisher,1,2,60,now+1 days,88); id = sink.grantDigest(g); vm.prank(owner); sink.revokeGrant(id); then sink.registerGrant(g, sign(OWNER,id)) emits PublisherGranted(id, publisher, 1, 2, expiresAt) (vm.expectEmit passes); afterwards sink.grant(id).revoked == true, sink.grantNonces(owner,88) == true, and vm.prank(owner); sink.cancelGrantNonce(88) reverts InvalidGrant.

### 9. Info: Design limit: a finalized replacement proven by the transaction engine is never surfaced to the API signal operation, which reports 'pending' indefinitely

`src/signals/index.mjs:115`

```
    if (observed.state === 'pending') return view(row);
```

adapters/sepolia-transactions.mjs:132-144 correctly releases an expired unknown transaction only when finalizedReplacement() (adapters/transaction-replacement.mjs) finds the exact competing sender+nonce transaction with a canonical finalized receipt, records {state:'cancelled', replacement} and never sends a cancellation; verified by test/transaction-replacement.test.mjs and by reading the binary search and receipt/block-hash checks. By then the original hash was already attached to the signal operation by the pipeline's signal_submit call while the row was 'submitted'/'unknown'. chain.observe() for that hash keeps returning {state:'pending'} (TransactionNotFoundError), so signal_operation and signal_latest consumers see a permanently pending operation while the host ledger holds definitive evidence that it can never execute. This matches the documented rule ('pending does not mean failure; hashes are never replaced') and no state is lost, so it is a reporting gap, not a defect: the engine's replacement evidence (competing hash, finalized block) could be propagated to the operation as a terminal 'replaced' outcome without inventing or replacing a hash.

**Reproduction**

State: signal operation op1 (kind record) with transactionHash H attached; chain-transactions row 'signal-record:op1' in state 'unknown' with intent.expiresAt passed; finalized chain contains a different transaction H2 from the same publisher with the same nonce. transactions.reconcile('signal-record:op1') -> row becomes {state:'cancelled', replacement:{transactionHash:H2,...}} and the pipeline run ends 'failed' with error 'signal_transaction_cancelled' (adapters/signal-publisher.mjs:81-83). signals.operation(actor, projectId, op1) afterwards -> reconcile() -> chain.observe(action,H) returns {state:'pending'} (adapters/signal-read.mjs:176) -> line 115 returns the row unchanged, forever. Expected from a consumer's point of view: a terminal state carrying the replacement evidence; actual: 'pending' with no path to any terminal state.

### 10. Info: Observation publication expiry can fall below the public-result service's now+1000 ms floor, raising an unclassified invalid_integer that the scheduler retries every 60 s until the run deadline

`reference/pulse-pipeline.mjs:34`

```
   step.expiresAt=Math.min((observedAt+run.rule.maximumAge)*1000,run.serviceExpiresAt);await checkpoint(run);
```

The publication expiry is derived from the observation plus the on-chain maximumAge, while src/services/public-results.mjs:87 enforces `integer(expiresAt, now() + 1000, manifest.validUntil)` against the API host's clock at publication time. reference/pulse-providers.mjs:28 only requires observedAt > floor(now/1000)-3600 at provider execution time, so with an on-chain maximumAge of 3600 (the value used by the project's own local fixture; the documented two-hour window cannot hit this) an observation accepted near that limit yields step.expiresAt <= now()+1000 by the time the consumer pipeline publishes it, and service_result_publish throws invalid_integer. That code is not in the scheduler's terminal list (reference/pulse-scheduler.mjs:50-53) and line 69's pulse_result_expiring check is never reached, so the job is re-queued with retryAt now+60000 and re-fails identically (step.expiresAt is checkpointed) until run.expiresAt passes and `run.expiresAt<=now()` makes it terminal. Needs a finality stall of roughly 45 minutes or more; bounded by one interval and wastes only reference API calls; no state is corrupted. Fix: treat invalid_integer from service_result_publish as terminal (or compute step.expiresAt with the same margin as line 69 and fail pulse_result_expiring), or reject observations whose observedAt+maximumAge is within a margin of now before accepting the delivered order.

**Reproduction**

Code trace with concrete values: maximumAge = 3600, observer executes at t0 = 1800000000000 ms with finalized block timestamp 1799996401 (3599 s old; passes `observedAt > floor(now/1000)-3600` at reference/pulse-providers.mjs:28). Consumer pipeline reaches line 34 at t0+30000: step.expiresAt = min((1799996401+3600)*1000, serviceExpiresAt) = 1800001000 ms. publicResult() -> service_result_publish -> src/services/public-results.mjs:87 integer(1800001000, 1800030000+1000, ...) -> invalid_integer. Scheduler: not in the terminal list and run.expiresAt > now, so finish(state:'waiting', retryAt: now+60000); each retry reuses the checkpointed step.expiresAt and fails again until run.expiresAt (<= intervalMs) passes.

---

Judge's submission `8b97a77035e7dc97173b9ff374f442bfc90c6787381964ca02cef5a423bfeef3`, accepted on the IdentityMD network. Acceptance means the report met the job's checks;
it is not a guarantee that the code has no other defects.
