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
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ A source-changing hardening release is deployed and certified on Studio Next:
`dfb564fbd644fae756808fee2afc1f43c35d0dde095e33ad9f4802569e80007a`
- Direct Runtime: **117 passed**
- Python non-runtime: **454 passed + 334 subtests**
- frontend Vitest: **43 passed**
- frontend Vitest: **46 passed**
- browser E2E: **12 passed**

The candidate rejects future-dated evidence, exposes the exact helper binding,
Expand Down Expand Up @@ -213,12 +213,15 @@ ABORT branches and one-time entitlement consumption. It does not claim
authenticated proof of downstream external delivery or production-grade
retry/reconciliation for failed external transfers.

The frontend now provides a fail-closed operator observation of triggered child
transactions. It reports `DELIVERED` only for an exact one-child match with
`FINALIZED` plus `FINISHED_WITH_RETURN`, reports `FAILED` only for an exact
match with `FINISHED_WITH_ERROR`, and otherwise reports `PENDING` or
`UNVERIFIED`. This read-only observation does not change the contract boundary
or authorize a retry.
The frontend now provides a fail-closed operator observation of claim parents,
their exact native outbound message, and any triggered child transaction. It
reports `DELIVERED` only after the parent is independently bound to the
certified coordinator, exact `claim_mission` call, connected beneficiary, and
one exact outbound message, followed by an exact child match with `FINALIZED`
plus `FINISHED_WITH_RETURN`. A finalized child execution error is reported as
`FINALIZED_ERROR`, not proof of terminal non-delivery. Missing, ambiguous, or
unreadable child data remains `UNVERIFIED`. This read-only observation does not
change the contract boundary or authorize a retry.

See [`docs/OPEN_QUESTIONS.md`](./docs/OPEN_QUESTIONS.md) for the concise submission boundary.

Expand Down
14 changes: 7 additions & 7 deletions components/application/application-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ export function ApplicationShell() {
</div>
) : (
<CaseRoom
key={lastMissionId || "case-room"}
key={`${lastMissionId || "case-room"}:${wallet.address}`}
wallet={wallet}
initialMissionId={lastMissionId}
/>
Expand Down Expand Up @@ -420,7 +420,7 @@ export function ApplicationShell() {
</div>
) : (
<FundMissionFlow
key={lastMissionId || "manual-funding"}
key={`${lastMissionId || "manual-funding"}:${wallet.address}`}
wallet={wallet}
initialMissionId={lastMissionId}
/>
Expand All @@ -443,7 +443,7 @@ export function ApplicationShell() {
</div>
) : (
<PrepareMissionFlow
key={lastMissionId || "manual-prepare"}
key={`${lastMissionId || "manual-prepare"}:${wallet.address}`}
wallet={wallet}
initialMissionId={lastMissionId}
/>
Expand All @@ -466,7 +466,7 @@ export function ApplicationShell() {
</div>
) : (
<EvidenceMissionFlow
key={lastMissionId || "manual-evidence"}
key={`${lastMissionId || "manual-evidence"}:${wallet.address}`}
wallet={wallet}
initialMissionId={lastMissionId}
/>
Expand All @@ -489,7 +489,7 @@ export function ApplicationShell() {
</div>
) : (
<SealMissionFlow
key={lastMissionId || "manual-seal"}
key={`${lastMissionId || "manual-seal"}:${wallet.address}`}
wallet={wallet}
initialMissionId={lastMissionId}
/>
Expand All @@ -512,7 +512,7 @@ export function ApplicationShell() {
</div>
) : (
<ResolutionMissionFlow
key={lastMissionId || "manual-resolution"}
key={`${lastMissionId || "manual-resolution"}:${wallet.address}`}
wallet={wallet}
initialMissionId={lastMissionId}
/>
Expand All @@ -535,7 +535,7 @@ export function ApplicationShell() {
</div>
) : (
<ClaimMissionFlow
key={lastMissionId || "manual-claim"}
key={`${lastMissionId || "manual-claim"}:${wallet.address}`}
wallet={wallet}
initialMissionId={lastMissionId}
/>
Expand Down
77 changes: 59 additions & 18 deletions components/application/claim-mission-flow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ import {
type FinalizedClaimProof,
} from "@/lib/genlayer-claim";
import {
CURRENT_DEPLOYMENT_ANCHOR,
} from "@/lib/deployment-anchor";
import {
isTransactionHash,
observeExternalDelivery,
persistClaimTransaction,
readPersistedClaimTransaction,
Expand Down Expand Up @@ -84,6 +88,12 @@ export function ClaimMissionFlow({
wallet.address,
),
);
const [claimTransactionId, setClaimTransactionId] = useState(() =>
readPersistedClaimTransaction(
initialMissionId,
wallet.address,
)?.transactionId ?? "",
);
const [
delivery,
setDelivery,
Expand All @@ -101,6 +111,11 @@ export function ClaimMissionFlow({
&& claimRecord.beneficiary.toLowerCase() === wallet.address.toLowerCase()
? claimRecord
: null;
const activeParentClaimTransactionId = isTransactionHash(
claimTransactionId.trim(),
)
? claimTransactionId.trim()
: null;

function resetReview() {
setQuote(null);
Expand Down Expand Up @@ -162,6 +177,7 @@ export function ClaimMissionFlow({
transactionId: txId,
amount: quote.snapshot.missionClaimable,
});
setClaimTransactionId(txId);
persistClaimTransaction(
quote.snapshot.mission.missionId,
quote.snapshot.beneficiary,
Expand All @@ -181,6 +197,8 @@ export function ClaimMissionFlow({
wallet.client,
txId,
{
coordinator: CURRENT_DEPLOYMENT_ANCHOR.contractAddress,
missionId: quote.snapshot.mission.missionId,
recipient: quote.snapshot.beneficiary,
amount: quote.snapshot.missionClaimable,
},
Expand Down Expand Up @@ -215,10 +233,7 @@ export function ClaimMissionFlow({
}

async function refreshDelivery() {
if (
activeClaimRecord === null
|| deliveryBusy
) {
if (activeParentClaimTransactionId === null || deliveryBusy) {
return;
}

Expand All @@ -228,17 +243,19 @@ export function ClaimMissionFlow({
setDelivery(
await observeExternalDelivery(
wallet.client,
activeClaimRecord.transactionId,
activeParentClaimTransactionId,
{
coordinator: CURRENT_DEPLOYMENT_ANCHOR.contractAddress,
missionId,
recipient: wallet.address,
amount: activeClaimRecord.amount,
amount: activeClaimRecord?.amount,
},
),
);
} catch (caught: unknown) {
setDelivery(
unverifiedExternalDelivery(
activeClaimRecord.transactionId,
activeParentClaimTransactionId,
caught instanceof Error
? caught.message
: "The delivery observation could not be completed. No delivery outcome is claimed.",
Expand All @@ -254,8 +271,8 @@ export function ClaimMissionFlow({
): string {
return phase === "DELIVERED"
? "DELIVERED"
: phase === "FAILED"
? "FAILED — NO AUTOMATIC RETRY"
: phase === "FINALIZED_ERROR"
? "FINALIZED EXECUTION ERROR — DELIVERY UNRESOLVED"
: phase === "PENDING"
? "PENDING"
: "UNVERIFIED";
Expand Down Expand Up @@ -285,19 +302,33 @@ export function ClaimMissionFlow({
setMissionId(
nextMissionId,
);
setClaimRecord(
readPersistedClaimTransaction(
nextMissionId,
wallet.address,
),
const persisted = readPersistedClaimTransaction(
nextMissionId,
wallet.address,
);
setClaimRecord(persisted);
setClaimTransactionId(persisted?.transactionId ?? "");
resetReview();
}}
placeholder="Mission ID"
maxLength={512}
/>
</label>

<label className="claim-mission-input">
<span>Parent claim transaction (optional recovery)</span>
<input
value={claimTransactionId}
onChange={(event) => {
setClaimTransactionId(event.target.value);
setDelivery(null);
setError(null);
}}
placeholder="0x… paste a finalized claim transaction"
spellCheck={false}
/>
</label>

<div className="claim-rule-grid">
<div>
<ShieldCheck
Expand Down Expand Up @@ -522,7 +553,7 @@ export function ClaimMissionFlow({
</div>
) : null}

{activeClaimRecord !== null ? (
{activeParentClaimTransactionId !== null ? (
<div className="claim-delivery">
<div className="claim-delivery-heading">
<div>
Expand Down Expand Up @@ -559,7 +590,16 @@ export function ClaimMissionFlow({
<dl>
<div>
<dt>Parent claim</dt>
<dd>{activeClaimRecord.transactionId}</dd>
<dd>{activeParentClaimTransactionId}</dd>
</div>
<div>
<dt>Outbound message</dt>
<dd>
{delivery?.outboundMessage === null
|| delivery?.outboundMessage === undefined
? "Not verified"
: `${delivery.outboundMessage.recipient} · ${delivery.outboundMessage.amount.toString()} wei`}
</dd>
</div>
<div>
<dt>Triggered child</dt>
Expand Down Expand Up @@ -587,8 +627,9 @@ export function ClaimMissionFlow({
<small>{delivery.reason}</small>
) : (
<small>
Select “Observe child transaction” to read the exact child
transaction. No delivery outcome is assumed before then.
Select “Observe child transaction” to verify the parent claim,
outbound message, and any exact child transaction exposed by
Studio Next. No delivery outcome is assumed before then.
</small>
)}
</div>
Expand Down
5 changes: 3 additions & 2 deletions components/application/create-mission-flow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
type MissionDraft,
type TransactionProgress,
} from "@/lib/genlayer-browser";
import { writeBrowserStorage } from "@/lib/browser-storage";

type CreateMissionFlowProps = {
wallet: ConnectedCommitWallet;
Expand Down Expand Up @@ -256,7 +257,7 @@ export function CreateMissionFlow({
quote,
);

localStorage.setItem(
writeBrowserStorage(
"commit:last-transaction",
txId,
);
Expand All @@ -273,7 +274,7 @@ export function CreateMissionFlow({
);

if (finalResult.successful) {
localStorage.setItem(
writeBrowserStorage(
"commit:last-mission",
missionId,
);
Expand Down
3 changes: 2 additions & 1 deletion components/application/fund-mission-flow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
type MissionFundingSnapshot,
type TransactionProgress,
} from "@/lib/genlayer-browser";
import { writeBrowserStorage } from "@/lib/browser-storage";

type FundMissionFlowProps = {
wallet: ConnectedCommitWallet;
Expand Down Expand Up @@ -169,7 +170,7 @@ export function FundMissionFlow({
quote,
);

localStorage.setItem(
writeBrowserStorage(
"commit:last-transaction",
txId,
);
Expand Down
3 changes: 2 additions & 1 deletion components/application/resolution-mission-flow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
type RepairEvidenceQuote,
type ResolutionInspection,
} from "@/lib/genlayer-resolution";
import { writeBrowserStorage } from "@/lib/browser-storage";

type ResolutionMissionFlowProps = {
wallet: ConnectedCommitWallet;
Expand Down Expand Up @@ -249,7 +250,7 @@ export function ResolutionMissionFlow({
evaluateQuote,
);

localStorage.setItem(
writeBrowserStorage(
`commit:evaluation:${missionId}`,
txId,
);
Expand Down
15 changes: 7 additions & 8 deletions components/justice/case-room.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
} from "@/lib/genlayer-appeal";
import { AppealPanel } from "@/components/justice/appeal-panel";
import { EvidenceRecordCard } from "@/components/justice/evidence-record-card";
import { readBrowserStorage } from "@/lib/browser-storage";

type CaseRoomProps = {
wallet: ConnectedCommitWallet;
Expand Down Expand Up @@ -156,7 +157,7 @@ export function CaseRoom({
return "";
}

return window.localStorage.getItem(
return readBrowserStorage(
`commit:evaluation:${initialMissionId}`,
) ?? "";
});
Expand Down Expand Up @@ -247,10 +248,10 @@ export function CaseRoom({
);

setSubmittedAppeal(txId);
setAppealBusy(false);
void refreshCase();
await refreshCase();
} catch (caught: unknown) {
setAppealError(errorMessage(caught));
} finally {
setAppealBusy(false);
}
}
Expand All @@ -274,11 +275,9 @@ export function CaseRoom({
const nextMissionId = event.target.value;
setMissionId(nextMissionId);
setEvaluationTxId(
typeof window === "undefined"
? ""
: window.localStorage.getItem(
`commit:evaluation:${nextMissionId}`,
) ?? "",
readBrowserStorage(
`commit:evaluation:${nextMissionId}`,
) ?? "",
);
setCaseData(null);
setError(null);
Expand Down
2 changes: 1 addition & 1 deletion docs/AGENT_TANK_SUBMISSION.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ The earlier published release has the following historical certification record:
- helper source SHA-256:
`dfb564fbd644fae756808fee2afc1f43c35d0dde095e33ad9f4802569e80007a`;
- Python regression: **450 passed + 334 subtests**;
- frontend Vitest: **27 passed**;
- frontend Vitest: **46 passed**;
- browser E2E: **12 passed**;
- public `/`, `/app`, `/verify`, `/api/v1/health`: **HTTP 200**;
- public production bodies matched the promoted production clone during final
Expand Down
3 changes: 2 additions & 1 deletion docs/CURRENT_DEPLOYMENT_PROOF_2026-09-16.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ Local F18 evidence packet index SHA-256:

## Public application release

- Public URL: https://commitprotocol-genlayer.vercel.app
- Historical public URL: https://commitprotocol-genlayer.vercel.app
- Current canonical public URL: https://commit-protocol.vercel.app
- GitHub main: `e9858985495111cf2f21db6dc847c7f75b79c0da`
- Vercel production deployment ID: `dpl_6x9XzgAxPsRX8jXr7d6KonmEmL6S`
- Production release tree: `3627b57a0baaebda07b15da76cb8b594ee4a18f9`
Expand Down
Loading
Loading