From 202693cbc10d835166fe3bf6240fa56e529ff1cd Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 17:35:39 +0600 Subject: [PATCH 001/144] fix: harden verification and automate evidence-first Canton delivery Bind exact issue references and canonical merge commits; classify ledger rejections; add lifecycle/server regression tests, proof export UI, local DPM/Canton automation, CI and submission drafts. Local Node verification: 65 tests passed. Daml, live Canton, browser QA, final video and official submission gates remain explicitly unverified. --- .env.example | 23 +-- .github/workflows/ci.yml | 88 +++++---- .gitignore | 16 +- daml/daml/CommitLedger.daml | 6 + daml/daml/Test.daml | 5 + docs/AUTOMATED_RUNBOOK.md | 68 +++++++ docs/BUILD_STATUS.md | 76 +------- docs/DELIVERY_STATUS.md | 34 ++++ docs/DEMO_SCRIPT.md | 27 +++ docs/FINAL_CHECKLIST.md | 40 ++++ docs/RULES_RECHECK_20260929.md | 14 ++ docs/SUBMISSION_DRAFT.md | 37 ++++ evidence/.gitkeep | 0 scripts/bootstrap-dpm.sh | 25 ++- scripts/bootstrap-local.mjs | 29 +++ scripts/check-evidence.mjs | 15 ++ scripts/dar-package-id.py | 15 ++ scripts/record-demo.py | 71 +++++++ scripts/run-local-proof.sh | 46 +++++ scripts/verify-all.sh | 42 ++--- src/canton-json-api.mjs | 25 ++- src/github-verifier.mjs | 89 +++++++-- src/ledger-errors.mjs | 27 +++ src/orchestrator.mjs | 81 ++++---- src/run-full-demo.mjs | 41 ++-- src/server.mjs | 279 ++++++++-------------------- tests/github-verifier.test.mjs | 5 +- tests/lifecycle-transport.test.mjs | 91 +++++++++ tests/security-regressions.test.mjs | 118 ++++++++++++ tests/server.test.mjs | 28 +++ web/app.js | 193 +++++-------------- web/index.html | 148 ++------------- web/styles.css | 2 +- 33 files changed, 1067 insertions(+), 737 deletions(-) create mode 100644 docs/AUTOMATED_RUNBOOK.md create mode 100644 docs/DELIVERY_STATUS.md create mode 100644 docs/DEMO_SCRIPT.md create mode 100644 docs/FINAL_CHECKLIST.md create mode 100644 docs/RULES_RECHECK_20260929.md create mode 100644 docs/SUBMISSION_DRAFT.md create mode 100644 evidence/.gitkeep mode change 100644 => 100755 scripts/bootstrap-dpm.sh create mode 100644 scripts/bootstrap-local.mjs create mode 100644 scripts/check-evidence.mjs create mode 100755 scripts/dar-package-id.py create mode 100755 scripts/record-demo.py create mode 100755 scripts/run-local-proof.sh mode change 100644 => 100755 scripts/verify-all.sh create mode 100644 src/ledger-errors.mjs create mode 100644 tests/lifecycle-transport.test.mjs create mode 100644 tests/security-regressions.test.mjs create mode 100644 tests/server.test.mjs diff --git a/.env.example b/.env.example index 1d9e4bc..03eacf3 100644 --- a/.env.example +++ b/.env.example @@ -1,27 +1,18 @@ -# Canton JSON Ledger API. -CANTON_JSON_API_URL=http://localhost:3975 +# Secrets stay in your shell or an ignored local .env file, never in Git. +GITHUB_TOKEN= +CANTON_JSON_API_URL=http://127.0.0.1:3975 CANTON_PACKAGE_ID= - -# Three distinct Canton parties are required for the full competition proof. CANTON_MAINTAINER_PARTY= CANTON_CONTRIBUTOR_PARTY= CANTON_VERIFIER_PARTY= - -# LocalNet may use one token with rights for all demo parties. CANTON_TOKEN= - -# Or provide role-specific tokens for stronger separation. CANTON_MAINTAINER_TOKEN= CANTON_CONTRIBUTOR_TOKEN= CANTON_VERIFIER_TOKEN= - -# Optional GitHub token. Required while this repository is private. -GITHUB_TOKEN= - -# Optional full-demo overrides. +CANTON_USER_ID= +# Explicit local sandbox only. Never enable for a remote/production service. +CANTON_INSECURE_LOCAL=false COMMITLEDGER_ISSUE_URL=https://github.com/Saidur-droid/CommitLedger/issues/5 COMMITLEDGER_PR_NUMBER=6 COMMITLEDGER_CONTRIBUTOR_GITHUB=Saidur-droid -COMMITLEDGER_REWARD_AMOUNT=100 -COMMITLEDGER_BASE_BRANCH=main -COMMITLEDGER_EVIDENCE_FILE= +COMMITLEDGER_EVIDENCE_FILE=./evidence/canton-proof.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ca7dee..5127877 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,54 +1,76 @@ -name: Manual Verification - +name: Verified Delivery on: + push: + branches: [main, 'fix/**'] + pull_request: workflow_dispatch: - permissions: contents: read - + issues: read + pull-requests: read +concurrency: + group: verification-${{ github.ref }} + cancel-in-progress: true jobs: - node-verifier: + node: runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: "22" - - run: npm test - - daml: + node-version: '22' + - name: Run every Node test + run: | + mkdir -p evidence + set -o pipefail + npm test 2>&1 | tee evidence/node-tests.log + - uses: actions/upload-artifact@v4 + if: always() + with: + name: node-evidence-${{ github.sha }} + path: evidence/node-tests.log + canton-proof: + # Untrusted fork PRs never receive a private-repository read token. + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-latest + timeout-minutes: 25 steps: - uses: actions/checkout@v4 - - name: Install DPM SDK 3.5.12 - run: | - curl -sSL https://get.digitalasset.com/install/install.sh | DPM_EDITION=open-source bash -s 3.5.12 - echo "$HOME/.dpm/bin" >> "$GITHUB_PATH" - - name: Build Daml - working-directory: daml - run: DPM_SDK_VERSION=3.5.12 dpm build --all - - name: Test Daml - working-directory: daml - run: DPM_SDK_VERSION=3.5.12 dpm test - + - uses: actions/setup-node@v4 + with: + node-version: '22' + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '21' + - name: Install pinned open-source DPM + run: bash scripts/bootstrap-dpm.sh + - name: Build, test, deploy and capture a real local Canton lifecycle + env: + GITHUB_TOKEN: ${{ github.token }} + run: bash scripts/run-local-proof.sh + - uses: actions/upload-artifact@v4 + if: always() + with: + name: canton-evidence-${{ github.sha }} + path: | + evidence/*.log + evidence/*.json + if-no-files-found: warn + retention-days: 30 repository-gates: runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@v4 - - name: Required competition files exist + - name: Required files and constraints run: | test -f LOCKED_DECISIONS.md test -f docs/COMPETITION_COMPLIANCE.md - test -f docs/WINNING_STANDARD.md - test -f docs/RULES_SNAPSHOT.md + test -f docs/EVIDENCE_MANIFEST.md + test -f docs/DEMO_SCRIPT.md + test -f docs/SUBMISSION_DRAFT.md test -f daml/daml/CommitLedger.daml - test -f daml/daml/Test.daml - test -f src/github-verifier.mjs - test -f src/canton-json-api.mjs - test -f web/index.html - - name: No forbidden runtime dependencies - run: | - ! grep -RniE 'vercel|supabase' package.json src web scripts daml || { - echo "Forbidden runtime dependency reference detected" - exit 1 - } + ! grep -RniE 'vercel|supabase' package.json src web scripts daml + ! git ls-files | grep -E '(^|/)\.env(\.local)?$' diff --git a/.gitignore b/.gitignore index 9b2e96c..7b6a436 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,10 @@ -.DS_Store -.env -.env.* -!.env.example node_modules/ -coverage/ -dist/ +.env +.env.local +.env.*.local daml/.daml/ -*.dar -*.log +evidence/*.log +evidence/*.json +!evidence/.gitkeep +*.webm +*.mp4 diff --git a/daml/daml/CommitLedger.daml b/daml/daml/CommitLedger.daml index 6c0721b..3c0a21e 100644 --- a/daml/daml/CommitLedger.daml +++ b/daml/daml/CommitLedger.daml @@ -1,5 +1,7 @@ module CommitLedger where +import qualified DA.Text as Text + -- CommitLedger intentionally models a demo/test settlement asset. -- It does not claim to transfer fiat, Canton Coin, or production funds. @@ -19,6 +21,7 @@ data MergeEvidence = MergeEvidence prNumber : Int prUrl : Text headSha : Text + mergeCommitSha : Text baseBranch : Text merged : Bool mergedAt : Text @@ -175,6 +178,7 @@ template SubmittedBounty assertMsg "evidence URL mismatch" (evidence.prUrl == pullRequest.prUrl) assertMsg "evidence head SHA mismatch" (evidence.headSha == pullRequest.headSha) assertMsg "evidence base branch mismatch" (evidence.baseBranch == pullRequest.baseBranch) + assertMsg "merge commit SHA must have 40 characters" (Text.length evidence.mergeCommitSha == 40) assertMsg "merge timestamp is required" (evidence.mergedAt /= "") assertMsg "evidence hash is required" (evidence.evidenceHash /= "") create VerifiedBounty with @@ -229,6 +233,7 @@ template VerifiedBounty pullRequest rewardAmount rewardUnit + mergeCommitSha = evidence.mergeCommitSha evidenceHash = evidence.evidenceHash settledAt settlementRef @@ -245,6 +250,7 @@ template SettlementReceipt pullRequest : GitHubPullRequestRef rewardAmount : Decimal rewardUnit : Text + mergeCommitSha : Text evidenceHash : Text settledAt : Time settlementRef : Text diff --git a/daml/daml/Test.daml b/daml/daml/Test.daml index 4106d40..93ba041 100644 --- a/daml/daml/Test.daml +++ b/daml/daml/Test.daml @@ -49,6 +49,7 @@ mkEvidence = MergeEvidence with prUrl = "https://github.com/Saidur-droid/CommitLedger/pull/7" headSha = "abc123" baseBranch = "main" + mergeCommitSha = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" merged = True mergedAt = "2026-09-27T00:00:00Z" evidenceHash = "sha256:demo-evidence" @@ -87,6 +88,7 @@ testHappyPath = do receipt.bountyId === "bounty-001" receipt.rewardAmount === 100.0 receipt.rewardUnit === "DEMO_CREDIT" + receipt.mergeCommitSha === "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" receipt.evidenceHash === "sha256:demo-evidence" receipt.settlementRef === "demo-settlement-001" @@ -128,6 +130,9 @@ testBadEvidenceFails = do submitMustFail verifier do exerciseCmd submittedCid SubmittedBounty_VerifyMerged with evidence = mkEvidence with issueNumber = 999 + submitMustFail verifier do + exerciseCmd submittedCid SubmittedBounty_VerifyMerged with + evidence = mkEvidence with mergeCommitSha = "" testSettlementAuthorizationAndReplayProtection : Script () testSettlementAuthorizationAndReplayProtection = do diff --git a/docs/AUTOMATED_RUNBOOK.md b/docs/AUTOMATED_RUNBOOK.md new file mode 100644 index 0000000..437b73d --- /dev/null +++ b/docs/AUTOMATED_RUNBOOK.md @@ -0,0 +1,68 @@ +# Automated verification and real Canton proof + +## Prerequisites + +Use an internet-connected Linux/macOS development machine with Node.js 22+, Java 21, Python 3, curl and Bash. Clone the private repository with your normal authenticated GitHub tooling, then check out `fix/verified-delivery-20260929`. Do not paste credentials into chat or commit them. + +The existing fixture is private: provide `GITHUB_TOKEN` through your shell/session secret store with read access to this repository. GitHub Actions uses its repository-scoped token. No paid API or hosting is required by these scripts. + +## Local commands + +```bash +npm test +bash scripts/bootstrap-dpm.sh +bash scripts/run-local-proof.sh +``` + +The last command reruns Node tests, compiles/tests Daml, extracts the package identifier from the built DAR, starts a fresh owned Canton sandbox, uploads that DAR, allocates three actual parties and a demo user, runs the real GitHub-to-ledger lifecycle, and validates the evidence structure. It stops on any failure. It refuses to reuse a server already listening on port 3975 and stops only the sandbox process it started. Default Canton ports 6865-6869 must also be free. + +`scripts/verify-all.sh` removes any stale verification marker before starting. `run-local-proof.sh` also removes stale final proof/setup files. Inspect log timestamps and the source commit; never mix artifacts from different runs. + +The generated `.env.canton-demo.local` contains local party IDs, package ID and user ID, NOT a bearer token. It is ignored by Git. It never replaces a personal `.env.local`. This environment is deliberately unauthenticated and loopback-only. Never expose it publicly; it is not production key management or an authenticated multi-organization deployment. + +## Keep a verified session open for the judge + +```bash +COMMITLEDGER_SERVE_AFTER_PROOF=true bash scripts/run-local-proof.sh +``` + +Only after the proof command succeeds does the UI start at `http://127.0.0.1:4173`. Leave this operator process running during the recording. Ctrl+C shuts down its sandbox. Each UI run creates a NEW demo bounty; repeated demo runs are not repeated payments of one contract. + +For an existing authenticated development ledger, export `.env.example` variables with the actual deployed package, three distinct parties, authorized role tokens, and ledger user ID, then run: + +```bash +export COMMITLEDGER_EVIDENCE_FILE=./evidence/canton-proof.json +npm run demo:full +npm start +``` + +The application does not automatically load `.env` files. Export the variables in the shell. The newly added Daml record fields require a fresh build/deployment. This delivery does not claim an in-place upgrade/migration of old contracts. + +## Evidence + +Preserve `node-tests.log`, `daml-build.log`, `daml-tests.log`, `verification.json`, `ledger-setup.json`, `canton-proof.json` and `canton-demo.log` from the same run. Negative evidence must contain recognized ledger codes and the relevant assertion/contract details. A timeout or HTTP authentication error is not a security rejection. `check-evidence.mjs` checks structure only; genuine provenance comes from the recorded API responses and rerunning against the ledger. + +## Automated CI + +The workflow has Node, Canton-proof and repository-gate jobs and triggers on main/fix branch pushes, pull requests and manual runs. It retains log/evidence artifacts even when a job fails. A private repository's Actions availability and account policies still apply. No billing or visibility setting is changed by this workflow. + +## Optional browser video capture + +Install Playwright only as a development tool outside the application dependencies: + +```bash +python3 -m pip install playwright +python3 -m playwright install chromium +python3 scripts/record-demo.py +``` + +Run this from a second terminal while the verified operator session above is still open. The recorder calls the actual UI, waits for a real successful API response and only retains a video when receipt/rejection checks succeed. Failed captures are discarded. The output is a silent technical walkthrough, not a finished narrated submission video. Check the organizer's format/duration rules before editing or submitting it. + +## Technical references + +- https://docs.digitalasset.com/build/3.4/component-howtos/application-development/dpm-sandbox.html +- https://docs.digitalasset.com/build/3.4/explanations/json-api/index.html +- https://docs.digitalasset.com/build/3.5/reference/json-api/openapi.html +- https://archived.docs.digitalasset.com/operate/3.5/reference/error_codes.html + +The pinned project bundle remains 3.5.12. These references guide API/command shapes; only a successful run with the pinned bundle establishes compatibility. diff --git a/docs/BUILD_STATUS.md b/docs/BUILD_STATUS.md index e6c79e6..60deda7 100644 --- a/docs/BUILD_STATUS.md +++ b/docs/BUILD_STATUS.md @@ -1,77 +1,7 @@ # Build Status -_Last updated: 2026-09-27._ +Last reviewed: 2026-09-29. -## Implemented +See [Delivery status](DELIVERY_STATUS.md) for the current evidence-based status, [Automated runbook](AUTOMATED_RUNBOOK.md) for execution, and [Final checklist](FINAL_CHECKLIST.md) for release/submission gates. -- Two locked goals remain the governing gates: verified competition compliance and #1-caliber judge quality. -- Role-separated Daml lifecycle: Maintainer -> Contributor -> Verifier -> Maintainer receipt. -- Explicit revision / resubmission path. -- Evidence binding across repository, source issue number, PR number, PR URL, head SHA, base branch and merge timestamp. -- Daml rejection cases for wrong issue, wrong SHA, unmerged work, unauthorized verification, unauthorized settlement and replay. -- Canton JSON Ledger API v2 client with active-contract discovery. -- Full lifecycle orchestrator that automatically carries real contract IDs from stage to stage. -- Runtime negative checks for wrong-issue evidence, unauthorized settlement and duplicate settlement. -- Final active `SettlementReceipt` discovery and proof bundle. -- Local judge UI with real update-ID / contract-ID timeline and receipt view. -- One-command CLI: `npm run demo:full`. -- Real GitHub fixture: open issue #5 + merged PR #6 that references #5 without closing it. -- Free/local-first runtime configuration; no Vercel, Supabase or paid API dependency. - -## Verified external fixture - -As of 2026-09-27: -- issue #5 is open; -- PR #6 is merged; -- PR #6 body references issue #5; -- repository, PR and issue all belong to `Saidur-droid/CommitLedger`. - -This fixture is competition/demo evidence, not user traction. - -## Toolchain baseline - -CommitLedger pins the stable open-source DPM SDK bundle **3.5.12**. - -The current upstream 3.5 assembly manifest used during this build reports: -- SDK bundle: 3.5.12; -- Canton open source: 3.5.19; -- damlc: 3.5.3; -- Daml Script: 3.5.3. - -## Test status - -An earlier Node verifier baseline was executed successfully (3 passed, 0 failed). - -Since then the repository test suite has been expanded substantially to cover: -- domain transitions and distinct role enforcement; -- issue-bound GitHub evidence; -- Canton command builders; -- active-contract parsing; -- runtime configuration. - -Do **not** describe the expanded suite as green until `npm test` is rerun in an execution environment with the current branch. - -## Runtime evidence gate - -The current ChatGPT sandbox cannot install the official DPM bundle from the Digital Asset distribution endpoint, and the private repository's GitHub-hosted Actions jobs have previously failed before runner steps execute. - -Therefore these are intentionally not marked verified yet: -- current Daml compile success; -- current Daml Script test success; -- DAR deployment to Canton LocalNet; -- real full lifecycle execution through `SettlementReceipt`; -- captured runtime update IDs / contract IDs; -- captured runtime negative-check failures; -- final demo recording. - -The repository contains the commands required to produce those artifacts on an internet-connected Canton development environment: - -```bash -bash scripts/bootstrap-dpm.sh -bash scripts/verify-all.sh -npm run demo:full -``` - -## Submission rule - -CommitLedger may only be called **final submission ready** after every runtime item in `docs/EVIDENCE_MANIFEST.md` has real captured evidence. +Original Node suite: 16 passed. Expanded delivery Node suite: 65 passed. Daml build/tests, real Canton lifecycle evidence, full browser QA, final video, and official portal recheck remain unverified. CI configuration is not CI success. Do not claim 100% or submission-ready before those gates pass. diff --git a/docs/DELIVERY_STATUS.md b/docs/DELIVERY_STATUS.md new file mode 100644 index 0000000..570011d --- /dev/null +++ b/docs/DELIVERY_STATUS.md @@ -0,0 +1,34 @@ +# Delivery status - 29 September 2026 + +Base: `27bd415c862fd220994739a618c7bafc8e181bd5`. Delivery branch: `fix/verified-delivery-20260929`. + +## Executed successfully + +The original five Node test files were reconstructed from the GitHub connector and checked against their Git blob hashes. The full original suite passed: **16 tests, 0 failures**, Node.js 22.16.0. After the changes, the expanded suite passed: **65 tests, 0 failures**. The expanded suite includes real local HTTP server tests and mocked provider/ledger transport tests; it is NOT live Canton proof. + +## Implemented and Node-tested + +- Repository-qualified issue binding rejects foreign repository references, foreign URLs and misleading Markdown labels. References in code/comments are excluded. This proves an explicit issue mention, not semantic completion of the issue. +- Merge commit SHA is required, independently looked up through the GitHub commit endpoint, checked for reachability from the target branch, and included in the evidence hash. +- Structured Canton errors are required for negative proofs. Network, unknown, authentication, throttling and unrelated errors do not count as authorization/replay evidence. +- Unique demo-run bounty identities and unambiguous active-contract lookup prevent selecting a previous demo receipt. +- Canonical GitHub checks finish before any ledger write. +- HTTP server blocks arbitrary browser ledger-command submission, checks origins/content types, and keeps configuration separate from actual ledger health. +- Proof UI has readable steps, full identifiers, explicit negative responses and JSON export. Its browser-level visual validation is still pending. + +## Implemented but not runtime-verified + +Daml merge-SHA propagation and additional negative assertion; pinned DPM bootstrap; build/test/deploy/party/user allocation scripts; fail-closed evidence checker; push/PR/manual GitHub Actions jobs; optional real-browser recording script. + +## Blocking evidence still missing + +- DPM installation, current Daml build and Daml Script execution. The local execution environment has no DPM and cannot resolve the official SDK download host. `scripts/verify-all.sh` correctly exits 2 rather than inventing success. +- Actual Canton deployment, six ledger updates/contracts, final receipt and specific ledger rejection responses. +- Successful complete GitHub Actions run for this delivery commit. A configured workflow is not a successful run. +- Browser/mobile visual QA. The available browser rejected local navigation with `ERR_BLOCKED_BY_ADMINISTRATOR`; this restriction was not bypassed. +- Final recorded runtime demo video. Recording instructions and code are not a recorded video. +- Current official portal/rulebook verification and account registration/eligibility confirmation. Public portal retrieval did not expose the formal submission rules. Earlier deadline notes are historical, not a new official verification. + +The GitHub connection works. Remote Desktop Commander was suggested as a way to use an authorized development machine; it was not confirmed connected. No paid service, production deployment, permission broadening or repository-publication change was made. + +Do not close issue #4, claim 100% completion, or submit as runtime-verified until the missing evidence is produced. This status takes precedence over older implementation-only wording in other repository documents. diff --git a/docs/DEMO_SCRIPT.md b/docs/DEMO_SCRIPT.md new file mode 100644 index 0000000..d07c5e4 --- /dev/null +++ b/docs/DEMO_SCRIPT.md @@ -0,0 +1,27 @@ +# Demo script - evidence first + +Status: script prepared; actual verified-runtime video NOT yet recorded. Suggested internal length: about three minutes, adjustable to the official portal requirement, which is not yet verified. + +## Preparation + +Run the entire automated runbook on the delivery commit. Do not film a simulated lifecycle or reuse old IDs. Keep the successful real Canton session open. Hide tokens, terminals containing secrets and unrelated private information. Make GitHub fixture access available to the authorized judge without publishing credentials. + +## Storyboard and narration + +**Problem (opening):** "A merged pull request proves that code was accepted, but it does not by itself provide an auditable bounty settlement workflow. CommitLedger connects canonical GitHub evidence to role-authorized Daml transitions on Canton." + +**Real source:** Show issue #5 and merged PR #6, including the explicit issue reference. Explain that this is an internal demo fixture, not external traction. Show the canonical merge commit SHA. Explain that issue mention verification does not evaluate whether the code solves the issue. + +**Roles:** Show Maintainer, Contributor and Verifier. Explain who creates, claims, accepts, submits, verifies and settles. Clearly label this as a local test ledger with DEMO_CREDIT, not money or Canton Coin. + +**Live flow:** Click "Run live Canton lifecycle". Wait for the real backend response. Expand a full contract/update ID and the final settlement receipt. Match its merge SHA and evidence hash to the GitHub proof. Download the JSON proof. + +**Failures:** Expand the three rejection responses: wrong issue evidence, contributor attempting settlement, and replay of the consumed verified contract. Explain the exact error code and relevant assertion/contract ID. Do not count a network error as a rejected unauthorized transaction. + +**Reproducibility:** Show the tested commit, Node/Daml logs and automated runbook. Say only what the retained logs actually prove. + +**Close:** "GitHub supplies external work state; the verifier is a trusted oracle; Daml restricts the recorded transitions; Canton provides the inspectable receipt. Production custody, identity management and payment rails remain outside this demo." + +## Before publishing + +Confirm the official duration, file format and hosting rules. Watch the complete video, check IDs against the proof file, remove secrets, verify judge access, and keep the unedited technical capture alongside the final video. Do not present a storyboard or silent capture as an already accepted submission. diff --git a/docs/FINAL_CHECKLIST.md b/docs/FINAL_CHECKLIST.md new file mode 100644 index 0000000..8a37a12 --- /dev/null +++ b/docs/FINAL_CHECKLIST.md @@ -0,0 +1,40 @@ +# Final release and submission checklist + +A checked source-code item is not a checked runtime-evidence item. Keep proof attached to the exact delivery commit. + +## Completed in the delivery work + +- [x] Original Node suite executed (16 passed). +- [x] Expanded Node suite executed (65 passed). +- [x] Repository-bound issue references and regression cases implemented. +- [x] Canonical merge commit lookup, branch reachability and evidence binding implemented. +- [x] Network/auth/unknown errors excluded from ledger rejection proof. +- [x] Full lifecycle transport and local HTTP endpoint tests implemented/executed. +- [x] CI, local proof scripts, setup runbook, proof UI code and submission/video drafts prepared. + +## Technical release gates still open + +- [ ] Review and merge the delivery branch only after required checks succeed. +- [ ] Pinned DPM bundle installs in the actual runner environment. +- [ ] Current Daml compilation and Daml Script tests pass; retain logs. +- [ ] Fresh Canton starts, current DAR deploys, actual package ID is captured. +- [ ] Six real successful ledger transitions produce a matching final receipt. +- [ ] Wrong issue, unauthorized settlement and duplicate settlement return the expected ledger rejections. +- [ ] Reproduce from clean setup and check retries/failure cleanup. +- [ ] Review Daml authorization beyond happy-path source tests, including direct intermediate-template creation. +- [ ] Browser/mobile visual and interaction QA passes on actual permitted browser. +- [ ] No secrets in tracked files, logs, screenshots or video. +- [ ] Actual final video recorded and reviewed against retained proof. + +## Organizer/account gates still open + +- [ ] Registration and eligibility, solo/team status and prize eligibility confirmed from the actual account/portal. +- [ ] Deadline and timezone reverified from the official current rulebook. +- [ ] Required repository visibility, judge access and license checked. +- [ ] Pre-existing-code/build-period rules checked. +- [ ] Required network, sponsor technology and chosen-track rules checked. +- [ ] Video duration/format/hosting and live deployment requirements checked. +- [ ] Submission fields, intellectual-property terms and any identity/KYC requirements checked. +- [ ] Final submission reviewed by the participant and portal receipt retained. + +Do not close the live-Canton completion issue or mark final submission ready with any critical box still open. Repeated workflow attempts cannot override account restrictions or replace real evidence. diff --git a/docs/RULES_RECHECK_20260929.md b/docs/RULES_RECHECK_20260929.md new file mode 100644 index 0000000..9098732 --- /dev/null +++ b/docs/RULES_RECHECK_20260929.md @@ -0,0 +1,14 @@ +# Rules recheck - 29 September 2026 + +Result: **formal/current portal verification remains pending**. + +The earlier RULES_SNAPSHOT.md records a reported deadline of 9 October 2026, 23:59 UTC (10 October 2026, 05:59 Asia/Dhaka). That is a historical repository claim, not a freshly verified official deadline. It must not override the current registered-participant portal or rulebook. + +Recheck targets: +- https://appsfactory.cc/hackathons +- https://forum.canton.network/ +- Organizer opening-ceremony reference: https://www.youtube.com/watch?v=_PesNP65N7I + +In this execution, available portal retrieval exposed only a site shell and did not provide the formal rules or the user's registration state. Indexed secondary descriptions mention an October 9 submission date; older secondary descriptions contain different application dates. Neither is a substitute for the current organizer submission deadline and timezone. No assumption was promoted to VERIFIED. + +Keep the compliance gate blocked until official requirements are captured with the source URL, checked date, exact requirement and applicable track. Optional partner bounties must not be confused with universal entry requirements. No MainNet purchase, public-repository change or identity submission is authorized by an unverified third-party description. diff --git a/docs/SUBMISSION_DRAFT.md b/docs/SUBMISSION_DRAFT.md new file mode 100644 index 0000000..82a9171 --- /dev/null +++ b/docs/SUBMISSION_DRAFT.md @@ -0,0 +1,37 @@ +# CommitLedger - submission draft + +**Status: draft; do not submit until FINAL_CHECKLIST.md is complete.** Final portal fields and length limits must be confirmed by the registered participant. + +## One-line description + +CommitLedger binds verified GitHub work to a role-authorized bounty workflow and an auditable settlement receipt on Canton. + +## Problem and approach + +Open-source bounty participants need a consistent way to connect accepted work with an inspectable settlement decision. CommitLedger reads a real GitHub issue and pull request server-side, validates the repository, issue reference, contributor policy, target branch and merged commit, and binds that evidence to a Daml lifecycle. + +The implemented flow is Issue -> Bounty -> ClaimRequest -> ClaimedBounty -> SubmittedBounty -> VerifiedBounty -> SettlementReceipt. The maintainer, contributor and verifier have different choices. The verifier remains a trusted external oracle: Daml does not independently call GitHub. Hashing makes the evidence identifiable; it does not eliminate trust in that oracle. + +## Why Canton and Daml + +The intended demonstrated value is authorized state progression, inspectable contract/update references, and prevention of re-exercising the same consumed verified contract. That last property is per-contract replay protection, not a claim that an issue can never receive multiple separately created bounties. Demo runs intentionally have distinct identifiers. + +## Technical implementation + +Node.js backend and local judge UI; canonical GitHub API adapter; Daml contracts; Canton JSON Ledger API v2; proof export; automated Node/Daml verification and local proof scripts. The project has no application-package dependency on a paid hosting provider or external payment rail. + +## Evidence currently established + +The original Node test suite passed 16/16 locally. The expanded delivery suite passed 65/65 locally, including failure classification, canonical GitHub validation, local HTTP-server checks and simulated lifecycle transport. These transport tests do not establish live Canton deployment or settlement. + +## Runtime evidence to insert only after a successful run + +Insert the exact tested commit, DAR package ID, Canton environment, retained build/test logs, final receipt contract/update IDs, evidence JSON artifact, negative rejection responses, judge-accessible demo and video links. These artifacts are not yet claimed in this draft. No placeholder IDs or fabricated testimonials are supplied. + +## Scope and integrity + +DEMO_CREDIT is non-production test value. This build does not claim fiat, Canton Coin or MainNet transfer; external users; production custody; financial returns; organizer endorsement; or guaranteed competition performance. The source fixture is the project's own real issue #5 and PR #6, not user adoption. + +## Limitations and next production work + +Production identity/key separation, authenticated multi-organization operation, custody/payment integration, dispute handling, comprehensive adversarial contract review, monitoring, and audited deployment are outside this local demo. The current delivery's Daml/runtime compatibility remains to be proven on an executable Canton environment. diff --git a/evidence/.gitkeep b/evidence/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/scripts/bootstrap-dpm.sh b/scripts/bootstrap-dpm.sh old mode 100644 new mode 100755 index 82c8bf0..83e7f55 --- a/scripts/bootstrap-dpm.sh +++ b/scripts/bootstrap-dpm.sh @@ -1,17 +1,14 @@ #!/usr/bin/env bash set -euo pipefail - -SDK_VERSION="3.5.12" - -if command -v dpm >/dev/null 2>&1; then - echo "DPM already installed: $(dpm --version || true)" - exit 0 +export PATH="${DPM_HOME:-$HOME/.dpm}/bin:$PATH" +export DPM_EDITION=open-source +if ! command -v dpm >/dev/null 2>&1; then + installer=$(mktemp) + trap 'rm -f "$installer"' EXIT + curl --fail --show-error --location --connect-timeout 15 --max-time 120 \ + https://get.digitalasset.com/install/install.sh -o "$installer" + bash "$installer" 3.5.12 +else + dpm install 3.5.12 fi - -echo "Installing open-source DPM SDK ${SDK_VERSION}..." -curl -sSL https://get.digitalasset.com/install/install.sh | DPM_EDITION=open-source bash -s "${SDK_VERSION}" - -export PATH="$HOME/.dpm/bin:$PATH" - -echo "Installed:" -dpm --version +DPM_SDK_VERSION=3.5.12 dpm version diff --git a/scripts/bootstrap-local.mjs b/scripts/bootstrap-local.mjs new file mode 100644 index 0000000..756d263 --- /dev/null +++ b/scripts/bootstrap-local.mjs @@ -0,0 +1,29 @@ +// This bootstrap ONLY targets an explicitly unauthenticated loopback demo ledger. +import {writeFile} from 'node:fs/promises'; +import {randomUUID} from 'node:crypto'; +import {CantonJsonApi} from '../src/canton-json-api.mjs'; +const baseUrl=process.env.CANTON_JSON_API_URL || 'http://127.0.0.1:3975'; +const api=new CantonJsonApi({baseUrl,insecureLocal:true}); +const suffix=randomUUID().slice(0,8); +const env={CANTON_JSON_API_URL:baseUrl,CANTON_INSECURE_LOCAL:'true',CANTON_PACKAGE_ID:process.env.CANTON_PACKAGE_ID}; +if(!/^[a-f0-9]{64}$/.test(env.CANTON_PACKAGE_ID||'')) throw new Error('Actual DAR package ID is required'); +const parties=[]; +for(const role of ['MAINTAINER','CONTRIBUTOR','VERIFIER']) { + const result=await api.request('/v2/parties',{method:'POST',body:JSON.stringify({partyIdHint:`CommitLedger-${role}-${suffix}`,identityProviderId:''})}); + const party=result.partyDetails?.party; + if(!party) throw new Error(`Party allocation did not return partyDetails.party for ${role}`); + env[`CANTON_${role}_PARTY`]=party;parties.push(party); +} +const userId=`commitledger-demo-${suffix}`; +await api.request('/v2/users',{method:'POST',body:JSON.stringify({ + user:{id:userId,primaryParty:parties[0],identityProviderId:'',isDeactivated:false}, + rights:parties.flatMap(party=>[ + {kind:{CanActAs:{value:{party}}}}, {kind:{CanReadAs:{value:{party}}}} + ]) +})}); +env.CANTON_USER_ID=userId; +// Shell single-quote escaping. No bearer tokens are created or written here. +const quote=value=>"'"+String(value).replaceAll("'","'\\''")+"'"; +await writeFile('.env.canton-demo.local',Object.entries(env).map(([k,v])=>`${k}=${quote(v)}`).join('\n')+'\n',{mode:0o600}); +await writeFile('evidence/ledger-setup.json',JSON.stringify({environment:'local-sandbox-no-auth',packageId:env.CANTON_PACKAGE_ID,parties,userId},null,2)+'\n'); +console.log('Allocated three real demo parties and one local demo user; wrote .env.canton-demo.local.'); diff --git a/scripts/check-evidence.mjs b/scripts/check-evidence.mjs new file mode 100644 index 0000000..d1852d5 --- /dev/null +++ b/scripts/check-evidence.mjs @@ -0,0 +1,15 @@ +import {readFile} from 'node:fs/promises'; +import {buildMergeEvidence} from '../src/github-verifier.mjs'; +const verification=JSON.parse(await readFile('evidence/verification.json','utf8')); +if(!verification.node || !verification.damlBuild || !verification.damlTests) throw new Error('Full build/test verification is missing'); +const proof=JSON.parse(await readFile('evidence/canton-proof.json','utf8')); +if(proof.schemaVersion!==2 || !/^[a-f0-9]{64}$/.test(proof.packageId||'')) throw new Error('Real package ID and current proof schema are required'); +const names=['BOUNTY_ON_LEDGER','CLAIM_REQUESTED','CLAIMED','PR_SUBMITTED','VERIFIED','SETTLED']; +if(proof.steps?.length!==6 || proof.steps.some((s,i)=>s.name!==names[i] || !s.updateId || !s.contractId || !Number.isSafeInteger(s.completionOffset))) throw new Error('Six complete ledger transitions are required'); +if(new Set(proof.steps.map(s=>s.contractId)).size!==6) throw new Error('Contract IDs must be distinct'); +if(proof.negativeChecks?.length!==3 || proof.negativeChecks.some(c=>!c.rejected || !c.code || !c.response)) throw new Error('Three structured rejection responses are required'); +if(proof.settlementReceipt?.rewardUnit!=='DEMO_CREDIT') throw new Error('Expected clearly labelled demo credit receipt'); +const hash=buildMergeEvidence(proof.mergeEvidence).evidenceHash; +if(hash!==proof.mergeEvidence.evidenceHash || hash!==proof.settlementReceipt.evidenceHash) throw new Error('Receipt evidence hash mismatch'); +if(proof.settlementReceipt.mergeCommitSha!==proof.mergeEvidence.mergeCommitSha) throw new Error('Receipt merge commit mismatch'); +console.log('Technical evidence structure passes. This does not independently authenticate the ledger or clear organizer/video/submission gates.'); diff --git a/scripts/dar-package-id.py b/scripts/dar-package-id.py new file mode 100755 index 0000000..ae4f002 --- /dev/null +++ b/scripts/dar-package-id.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +"""Read the main package hash from the actual built DAR, never from a placeholder.""" +import re +import sys +import zipfile +with zipfile.ZipFile(sys.argv[1]) as dar: + manifest = dar.read('META-INF/MANIFEST.MF').decode('utf-8') +manifest = re.sub(r'\r?\n ', '', manifest) +main = re.search(r'^Main-Dalf:\s*(.+)$', manifest, re.MULTILINE) +if not main: + raise SystemExit('Main-Dalf missing from DAR manifest') +package = re.search(r'([a-f0-9]{64})\.dalf$', main.group(1).strip()) +if not package: + raise SystemExit('Cannot derive main package ID from DAR manifest') +print(package.group(1)) diff --git a/scripts/record-demo.py b/scripts/record-demo.py new file mode 100755 index 0000000..ff0d722 --- /dev/null +++ b/scripts/record-demo.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +"""Record only a successful real local UI run; no simulated responses or narration.""" +import json +from pathlib import Path +import shutil +import tempfile +from playwright.sync_api import sync_playwright + + +def main() -> None: + evidence = Path('evidence') + evidence.mkdir(exist_ok=True) + target = evidence / 'demo-runtime.webm' + if target.exists(): + raise RuntimeError('Existing recording retained; rename it before capturing a new run') + temp = Path(tempfile.mkdtemp(prefix='commitledger-video-')) + context = browser = None + try: + with sync_playwright() as p: + browser = p.chromium.launch() + context = browser.new_context(viewport={'width': 1440, 'height': 1000}, + record_video_dir=str(temp), + record_video_size={'width': 1440, 'height': 1000}) + page = context.new_page() + page.goto('http://127.0.0.1:4173', wait_until='networkidle') + page.locator('#live-run').wait_for() + if not page.locator('#live-run').is_enabled(): + raise RuntimeError('Real Canton lifecycle is not ready; no demo video retained') + page.wait_for_timeout(3000) + with page.expect_response(lambda r: r.url.endswith('/api/demo/run'), timeout=240000) as response: + page.locator('#live-run').click() + result = response.value + data = result.json() + proof = data.get('proof', {}) + if not result.ok or len(proof.get('steps', [])) != 6 or not proof.get('settlementReceipt'): + raise RuntimeError('Actual lifecycle did not produce six steps and a receipt') + checks = proof.get('negativeChecks', []) + if len(checks) != 3 or not all(c.get('rejected') and c.get('code') for c in checks): + raise RuntimeError('Actual negative-rejection evidence is missing') + page.locator('#receipt-card').wait_for(state='visible') + page.wait_for_timeout(4000) + for detail in page.locator('#timeline details, #receipt-card details').all(): + detail.locator('summary').click() + detail.scroll_into_view_if_needed() + page.wait_for_timeout(2500) + page.locator('#receipt-card').scroll_into_view_if_needed() + page.wait_for_timeout(5000) + video = page.video + context.close() + context = None + video.save_as(str(target)) + browser.close() + browser = None + (evidence / 'video-proof.json').write_text(json.dumps(proof, indent=2) + '\n') + print(f'Recorded a real local UI run: {target}; review before submission.') + finally: + if context: + try: + context.close() + except Exception: + pass + if browser: + try: + browser.close() + except Exception: + pass + shutil.rmtree(temp, ignore_errors=True) + + +if __name__ == '__main__': + main() diff --git a/scripts/run-local-proof.sh b/scripts/run-local-proof.sh new file mode 100755 index 0000000..7065a9f --- /dev/null +++ b/scripts/run-local-proof.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "$0")/.." +export PATH="${DPM_HOME:-$HOME/.dpm}/bin:$PATH" +export DPM_SDK_VERSION=3.5.12 +mkdir -p evidence +rm -f evidence/canton-proof.json evidence/ledger-setup.json +bash scripts/verify-all.sh +DAR="$PWD/daml/.daml/dist/commit-ledger-0.1.0.dar" +test -f "$DAR" +export CANTON_PACKAGE_ID +CANTON_PACKAGE_ID=$(python3 scripts/dar-package-id.py "$DAR") +export CANTON_JSON_API_URL=http://127.0.0.1:3975 +# Refuse to reuse an unrelated server. This script owns its sandbox process. +if curl -s --max-time 2 --connect-timeout 2 "$CANTON_JSON_API_URL/v2/state/ledger-end" >/dev/null; then + echo 'Port 3975 is already in use; stop that service before creating a fresh proof.' >&2 + exit 2 +fi +dpm sandbox --json-api-port 3975 --dar "$DAR" > evidence/canton-sandbox.log 2>&1 & +sandbox_pid=$! +trap 'kill "$sandbox_pid" 2>/dev/null || true; wait "$sandbox_pid" 2>/dev/null || true' EXIT +ready=false +for attempt in $(seq 1 180); do + if ! kill -0 "$sandbox_pid" 2>/dev/null; then + tail -n 60 evidence/canton-sandbox.log + exit 1 + fi + if curl --fail --silent --max-time 2 "$CANTON_JSON_API_URL/v2/state/ledger-end" > evidence/ledger-end.json; then + ready=true + break + fi + sleep 1 +done +if [ "$ready" != true ]; then echo 'Canton readiness check failed' >&2; exit 1; fi +node scripts/bootstrap-local.mjs +set -a +source .env.canton-demo.local +set +a +export COMMITLEDGER_EVIDENCE_FILE="$PWD/evidence/canton-proof.json" +# GITHUB_TOKEN is inherited for this private repository; it is never written to .env.canton-demo.local. +npm run demo:full 2>&1 | tee evidence/canton-demo.log +node scripts/check-evidence.mjs +if [ "${COMMITLEDGER_SERVE_AFTER_PROOF:-false}" = true ]; then + # Foreground operator session; Ctrl+C also cleans up the owned sandbox. + npm start +fi diff --git a/scripts/verify-all.sh b/scripts/verify-all.sh old mode 100644 new mode 100755 index af8d2aa..f5f2884 --- a/scripts/verify-all.sh +++ b/scripts/verify-all.sh @@ -1,31 +1,17 @@ #!/usr/bin/env bash set -euo pipefail - -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -cd "$ROOT" - -echo "== Node verifier tests ==" -node --version -npm test - -echo -echo "== Daml build ==" -export PATH="$HOME/.dpm/bin:$PATH" -command -v dpm >/dev/null 2>&1 || { - echo "DPM not found. Run: bash scripts/bootstrap-dpm.sh" +cd "$(dirname "$0")/.." +mkdir -p evidence +rm -f evidence/verification.json +export PATH="${DPM_HOME:-$HOME/.dpm}/bin:$PATH" +export DPM_SDK_VERSION=3.5.12 +node --version | tee evidence/node-version.log +npm test 2>&1 | tee evidence/node-tests.log +if ! command -v dpm >/dev/null 2>&1; then + echo 'BLOCKED: DPM is not installed. Run bash scripts/bootstrap-dpm.sh on an internet-connected development machine.' | tee evidence/daml-build.log exit 2 -} -( - cd daml - DPM_SDK_VERSION=3.5.12 dpm build --all -) - -echo -echo "== Daml tests ==" -( - cd daml - DPM_SDK_VERSION=3.5.12 dpm test -) - -echo -echo "All local verification gates passed." +fi +(cd daml && dpm version --active) 2>&1 | tee evidence/dpm-version.log +(cd daml && dpm build) 2>&1 | tee evidence/daml-build.log +(cd daml && dpm test) 2>&1 | tee evidence/daml-tests.log +node -e 'require("node:fs").writeFileSync("evidence/verification.json",JSON.stringify({node:true,damlBuild:true,damlTests:true,verifiedAt:new Date().toISOString(),commit:process.env.GITHUB_SHA||"local-working-tree"},null,2)+"\n")' diff --git a/src/canton-json-api.mjs b/src/canton-json-api.mjs index f3e8e2a..0925f8f 100644 --- a/src/canton-json-api.mjs +++ b/src/canton-json-api.mjs @@ -1,3 +1,5 @@ +import { CantonApiError } from "./ledger-errors.mjs"; + function requireText(value, name) { const text = String(value || "").trim(); if (!text) throw new Error(`${name} is required`); @@ -6,7 +8,7 @@ function requireText(value, name) { function authHeaders(token) { return { - Authorization: `Bearer ${requireText(token, "Canton token")}`, + ...(token ? { Authorization: `Bearer ${token}` } : {}), "Content-Type": "application/json" }; } @@ -41,15 +43,18 @@ export function extractCreatedEvents(activeContractsResponse) { } export class CantonJsonApi { - constructor({ baseUrl, token, applicationId = "commit-ledger" }) { + constructor({ baseUrl, token, userId = "", insecureLocal = false }) { this.baseUrl = requireText(baseUrl, "Canton base URL").replace(/\/$/, ""); - this.token = requireText(token, "Canton token"); - this.applicationId = applicationId; + const url = new URL(this.baseUrl); + if (insecureLocal && !["127.0.0.1", "localhost", "[::1]"].includes(url.hostname)) throw new Error("insecure Canton mode requires loopback"); + this.token = insecureLocal ? String(token || "") : requireText(token, "Canton token"); + this.userId = userId; } async request(path, options = {}) { const response = await fetch(`${this.baseUrl}${path}`, { ...options, + signal: options.signal || AbortSignal.timeout(30_000), headers: { ...authHeaders(this.token), ...(options.headers || {}) @@ -60,7 +65,7 @@ export class CantonJsonApi { if (text) { try { body = JSON.parse(text); } catch { body = { raw: text }; } } - if (!response.ok) throw new Error(`Canton JSON Ledger API failed: ${response.status} ${text}`); + if (!response.ok) throw new CantonApiError(response.status, body); return body; } @@ -70,14 +75,14 @@ export class CantonJsonApi { body: JSON.stringify({ commands, workflowId, - applicationId: this.applicationId, + userId: this.userId, commandId, deduplicationPeriod: { Empty: {} }, actAs, readAs, submissionId: commandId, disclosedContracts: [], - domainId: "", + synchronizerId: "", packageIdSelectionPreference: [] }) }); @@ -96,8 +101,10 @@ export class CantonJsonApi { async findActiveContract({ party, templateId, activeAtOffset, predicate = () => true }) { const raw = await this.activeContracts({ party, templateId, activeAtOffset }); const events = extractCreatedEvents(raw); - const event = events.find(candidate => predicate(candidate?.createArgument || {})); - if (!event) throw new Error(`Active contract not found for template ${templateId} at offset ${activeAtOffset}`); + const matches = events.filter(candidate => candidate.templateId === templateId && predicate(candidate?.createArgument || {})); + if (matches.length > 1) throw new Error(`Ambiguous active contracts for template ${templateId}`); + const event = matches[0]; + if (!event?.contractId) throw new Error(`Active contract not found for template ${templateId} at offset ${activeAtOffset}`); return event; } } diff --git a/src/github-verifier.mjs b/src/github-verifier.mjs index 6a3801e..9f6e045 100644 --- a/src/github-verifier.mjs +++ b/src/github-verifier.mjs @@ -17,11 +17,13 @@ export function parseGitHubIssueUrl(value) { } catch { throw new Error("Enter a valid GitHub issue URL"); } + if (url.protocol !== "https:" || url.username || url.password || url.port) throw new Error("Issue URL must use HTTPS without credentials or a custom port"); if (!["github.com", "www.github.com"].includes(url.hostname)) throw new Error("Issue URL must point to github.com"); const parts = url.pathname.split("/").filter(Boolean); if (parts.length !== 4 || parts[2] !== "issues") throw new Error("Use github.com/owner/repo/issues/123"); + if (!/^[1-9][0-9]*$/.test(parts[3])) throw new Error("Invalid GitHub issue number"); const issueNumber = Number(parts[3]); - if (!Number.isInteger(issueNumber) || issueNumber <= 0) throw new Error("Invalid GitHub issue number"); + if (!Number.isSafeInteger(issueNumber) || issueNumber <= 0) throw new Error("Invalid GitHub issue number"); return { owner: parts[0], repo: parts[1], repository: `${parts[0]}/${parts[1]}`, issueNumber }; } @@ -31,8 +33,12 @@ export function normalizeIssue(payload, repository) { if (!Number.isInteger(payload.number) || !payload.html_url || !payload.title) { throw new Error("Incomplete GitHub issue payload"); } + const canonical = parseGitHubIssueUrl(payload.html_url); + if (canonical.repository.toLowerCase() !== repository.toLowerCase() || canonical.issueNumber !== payload.number) { + throw new Error("canonical GitHub issue identity mismatch"); + } return { - repository, + repository: canonical.repository, issueNumber: payload.number, issueUrl: payload.html_url, title: payload.title, @@ -46,10 +52,11 @@ export async function fetchGitHubIssue({ issueUrl, token, requireOpen = true }) const parsed = parseGitHubIssueUrl(issueUrl); const response = await fetch( `https://api.github.com/repos/${encodeURIComponent(parsed.owner)}/${encodeURIComponent(parsed.repo)}/issues/${parsed.issueNumber}`, - { headers: githubHeaders(token) } + { headers: githubHeaders(token), signal: AbortSignal.timeout(30_000) } ); if (!response.ok) throw new Error(`GitHub issue lookup failed: ${response.status} ${response.statusText}`); const issue = normalizeIssue(await response.json(), parsed.repository); + if (issue.issueNumber !== parsed.issueNumber) throw new Error("GitHub issue number mismatch"); if (requireOpen && issue.state !== "open") throw new Error("Bounty source issue must be open"); return issue; } @@ -60,6 +67,7 @@ export function normalizePullRequest(payload) { const prNumber = payload.number; const prUrl = payload.html_url; const headSha = payload.head?.sha; + const mergeCommitSha = payload.merge_commit_sha ?? ""; const baseBranch = payload.base?.ref; const merged = payload.merged === true; const mergedAt = payload.merged_at ?? ""; @@ -70,7 +78,7 @@ export function normalizePullRequest(payload) { throw new Error("Incomplete GitHub pull request payload"); } - return { repository, prNumber, prUrl, headSha, baseBranch, merged, mergedAt, author, body }; + return { repository, prNumber, prUrl, headSha, mergeCommitSha, baseBranch, merged, mergedAt, author, body }; } export function assertExpectedPullRequest(pr, expected) { @@ -81,14 +89,22 @@ export function assertExpectedPullRequest(pr, expected) { if (expected.contributorGithub && pr.author.toLowerCase() !== expected.contributorGithub.toLowerCase()) { throw new Error("pull request author mismatch"); } - if (expected.issueNumber) { + if (!pr.merged) throw new Error("pull request is not merged"); + if (!pr.mergedAt || !Number.isFinite(Date.parse(pr.mergedAt))) throw new Error("merged pull request is missing valid merged_at"); + if (!/^[a-f0-9]{40}$/i.test(pr.headSha)) throw new Error("invalid head SHA"); + if (!/^[a-f0-9]{40}$/i.test(pr.mergeCommitSha)) throw new Error("merged pull request is missing valid merge commit SHA"); + if (expected.mergeCommitSha && pr.mergeCommitSha !== expected.mergeCommitSha) throw new Error("merge commit SHA mismatch"); + const prUrl = new URL(pr.prUrl); + if (prUrl.protocol !== "https:" || prUrl.hostname !== "github.com" || prUrl.username || prUrl.password || prUrl.port || + prUrl.pathname.toLowerCase() !== `/${pr.repository}/pull/${pr.prNumber}`.toLowerCase()) { + throw new Error("canonical pull request URL mismatch"); + } + if (expected.issueNumber !== undefined) { const issueNumber = Number(expected.issueNumber); - const issueRef = new RegExp("(?:#|issues/)" + issueNumber + "\\b", "i"); - if (!issueRef.test(pr.body)) throw new Error("pull request does not reference the bounty issue"); + if (!Number.isSafeInteger(issueNumber) || issueNumber <= 0) throw new Error("invalid bounty issue number"); + if (!referencesIssue(pr.body, expected.repository, issueNumber)) throw new Error("pull request does not reference the bounty issue"); pr.issueNumber = issueNumber; } - if (!pr.merged) throw new Error("pull request is not merged"); - if (!pr.mergedAt) throw new Error("merged pull request is missing merged_at"); return pr; } @@ -96,12 +112,14 @@ export function buildMergeEvidence(pr) { if (!Number.isInteger(pr.issueNumber) || pr.issueNumber <= 0) { throw new Error("verified pull request is missing the bounty issue number"); } + if (!pr.merged || !/^[a-f0-9]{40}$/i.test(pr.mergeCommitSha)) throw new Error("valid merged commit evidence is required"); const canonical = JSON.stringify({ repository: pr.repository, issueNumber: pr.issueNumber, prNumber: pr.prNumber, prUrl: pr.prUrl, headSha: pr.headSha, + mergeCommitSha: pr.mergeCommitSha, baseBranch: pr.baseBranch, merged: pr.merged, mergedAt: pr.mergedAt @@ -112,6 +130,7 @@ export function buildMergeEvidence(pr) { prNumber: pr.prNumber, prUrl: pr.prUrl, headSha: pr.headSha, + mergeCommitSha: pr.mergeCommitSha, baseBranch: pr.baseBranch, merged: pr.merged, mergedAt: pr.mergedAt, @@ -119,13 +138,55 @@ export function buildMergeEvidence(pr) { }; } -export async function fetchVerifiedPullRequest({ repository, prNumber, token, expected = {} }) { - const response = await fetch(`https://api.github.com/repos/${repository}/pulls/${prNumber}`, { - headers: githubHeaders(token) +/** Explicit issue references only; mentions are not proof of semantic issue resolution. */ +export function referencesIssue(body, repository, issueNumber) { + let found = false; + const expected = repository.toLowerCase(); + function inspectUrl(raw) { + try { + const url = new URL(raw.replace(/[.,;!?]+$/, "")); + if (url.protocol === "https:" && url.hostname === "github.com" && !url.username && !url.password && !url.port && + url.pathname.replace(/\/$/, "").toLowerCase() === `/${expected}/issues/${issueNumber}`) found = true; + } catch { /* Unknown URL shapes never become local references. */ } + } + let text = String(body || "").replace(/|```[\s\S]*?```|`[^`]*`/g, " "); + // Remove entire Markdown links so a misleading label cannot override its target. + text = text.replace(/\[[^\]]*\]\(([^\s)]+)(?:\s+"[^"]*")?\)/g, (_match, url) => { + inspectUrl(url); + return " "; + }); + // Consume even foreign URLs before looking for bare #number references. + text = text.replace(/(?:[a-z][a-z0-9+.-]*:\/\/|www\.)[^\s<>"'`()]+/gi, url => { + inspectUrl(url); + return " "; + }); + text = text.replace(/(?:^|[^\w./-])([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)#([1-9][0-9]*)(?![\w/-])/g, (_match, repo, number) => { + if (repo.toLowerCase() === expected && Number(number) === issueNumber) found = true; + return " "; + }); + const local = /(?:^|[\s([{,:;])#([1-9][0-9]*)(?=$|[\s)\]},.!?;:])/g; + for (const match of text.matchAll(local)) if (Number(match[1]) === issueNumber) found = true; + return found; +} + +async function githubJson(path, token) { + const response = await fetch(`https://api.github.com/repos/${path}`, { + headers: githubHeaders(token), signal: AbortSignal.timeout(30_000) }); if (!response.ok) throw new Error(`GitHub API failed: ${response.status} ${response.statusText}`); + return response.json(); +} - const pr = normalizePullRequest(await response.json()); - assertExpectedPullRequest(pr, { repository, prNumber, ...expected }); +export async function fetchVerifiedPullRequest({ repository, prNumber, token, expected = {} }) { + if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository) || !Number.isSafeInteger(prNumber) || prNumber <= 0) { + throw new Error("invalid repository or pull request number"); + } + const pr = normalizePullRequest(await githubJson(`${repository}/pulls/${prNumber}`, token)); + // Expected values cannot override the canonical repository/PR requested above. + assertExpectedPullRequest(pr, { ...expected, repository, prNumber }); + const commit = await githubJson(`${repository}/commits/${pr.mergeCommitSha}`, token); + if (commit.sha !== pr.mergeCommitSha) throw new Error("canonical merge commit SHA mismatch"); + const comparison = await githubJson(`${repository}/compare/${pr.mergeCommitSha}...${encodeURIComponent(pr.baseBranch)}`, token); + if (!["ahead", "identical"].includes(comparison.status)) throw new Error("merge commit is not reachable from the expected base branch"); return buildMergeEvidence(pr); } diff --git a/src/ledger-errors.mjs b/src/ledger-errors.mjs new file mode 100644 index 0000000..bff4d11 --- /dev/null +++ b/src/ledger-errors.mjs @@ -0,0 +1,27 @@ +/** Fail closed: connectivity, authentication and unknown errors are not security proof. */ +export class CantonApiError extends Error { + constructor(status, body) { + const code = typeof body?.code === "string" ? body.code : "UNKNOWN_CANTON_ERROR"; + const cause = typeof body?.cause === "string" ? body.cause : "Unrecognized Canton error response"; + super(`Canton JSON Ledger API failed: ${status} ${code}: ${cause}`); + this.name = "CantonApiError"; + this.status = status; + this.code = code; + this.body = body; + } +} + +export async function expectLedgerFailure(name, action, expected) { + try { + await action(); + } catch (error) { + const codeMatches = error instanceof CantonApiError && expected.codes.includes(error.code); + const statusMatches = error?.status >= 400 && error.status < 500 && ![401, 403, 408, 429].includes(error.status); + const detailMatches = !expected.contains || JSON.stringify(error?.body || {}).includes(expected.contains); + if (!codeMatches || !statusMatches || !detailMatches) { + throw new Error(`Unproven ledger rejection (${name}): ${error?.message || String(error)}`, { cause: error }); + } + return { name, rejected: true, code: error.code, httpStatus: error.status, response: error.body }; + } + throw new Error(`Expected Canton rejection did not occur: ${name}`); +} diff --git a/src/orchestrator.mjs b/src/orchestrator.mjs index da2be4b..5743c76 100644 --- a/src/orchestrator.mjs +++ b/src/orchestrator.mjs @@ -1,3 +1,5 @@ +import { randomUUID } from "node:crypto"; +import { expectLedgerFailure } from "./ledger-errors.mjs"; import { CantonJsonApi } from "./canton-json-api.mjs"; import { templateId, @@ -19,7 +21,7 @@ function required(value, name) { function offsetOf(result) { const offset = Number(result?.completionOffset); - if (!result?.updateId || !Number.isFinite(offset)) { + if (!result?.updateId || !Number.isSafeInteger(offset) || offset <= 0) { throw new Error("Canton submission did not return updateId and completionOffset"); } return offset; @@ -36,17 +38,8 @@ function proofStep(name, submission, event) { }; } -function roleClient(baseUrl, token) { - return new CantonJsonApi({ baseUrl, token }); -} - -async function expectLedgerFailure(name, action) { - try { - await action(); - } catch (error) { - return { name, rejected: true, error: error.message }; - } - throw new Error(`Expected Canton rejection did not occur: ${name}`); +function roleClient(baseUrl, token, runtime) { + return new CantonJsonApi({ baseUrl, token, userId: runtime.userId || "", insecureLocal: runtime.insecureLocal || false }); } async function submitAndFind({ @@ -75,8 +68,16 @@ async function submitAndFind({ export function runtimeConfigFromEnv(env = process.env) { const fallbackToken = env.CANTON_TOKEN || ""; + const insecureLocal = env.CANTON_INSECURE_LOCAL === "true"; + const baseUrl = required(env.CANTON_JSON_API_URL, "CANTON_JSON_API_URL"); + if (insecureLocal && !["localhost", "127.0.0.1", "[::1]"].includes(new URL(baseUrl).hostname)) { + throw new Error("insecure Canton mode requires loopback"); + } + const token = (value, label) => insecureLocal ? String(value || "") : required(value, label); return { - baseUrl: required(env.CANTON_JSON_API_URL, "CANTON_JSON_API_URL"), + insecureLocal, + userId: env.CANTON_USER_ID || "", + baseUrl, packageId: required(env.CANTON_PACKAGE_ID, "CANTON_PACKAGE_ID"), parties: assertDistinctParties({ maintainer: env.CANTON_MAINTAINER_PARTY, @@ -84,9 +85,9 @@ export function runtimeConfigFromEnv(env = process.env) { verifier: env.CANTON_VERIFIER_PARTY }), tokens: { - maintainer: required(env.CANTON_MAINTAINER_TOKEN || fallbackToken, "CANTON_MAINTAINER_TOKEN or CANTON_TOKEN"), - contributor: required(env.CANTON_CONTRIBUTOR_TOKEN || fallbackToken, "CANTON_CONTRIBUTOR_TOKEN or CANTON_TOKEN"), - verifier: required(env.CANTON_VERIFIER_TOKEN || fallbackToken, "CANTON_VERIFIER_TOKEN or CANTON_TOKEN") + maintainer: token(env.CANTON_MAINTAINER_TOKEN || fallbackToken, "CANTON_MAINTAINER_TOKEN or CANTON_TOKEN"), + contributor: token(env.CANTON_CONTRIBUTOR_TOKEN || fallbackToken, "CANTON_CONTRIBUTOR_TOKEN or CANTON_TOKEN"), + verifier: token(env.CANTON_VERIFIER_TOKEN || fallbackToken, "CANTON_VERIFIER_TOKEN or CANTON_TOKEN") } }; } @@ -102,8 +103,11 @@ export async function runFullLifecycle({ now = () => new Date() }) { const { baseUrl, packageId, parties, tokens } = runtime; + const runId = randomUUID(); + contributorGithub = required(contributorGithub, "contributorGithub"); const issue = await fetchGitHubIssue({ issueUrl, token: githubToken }); const bounty = validateBountyDraft({ + bountyId: `demo-${runId}`, repository: issue.repository, issueNumber: issue.issueNumber, issueUrl: issue.issueUrl, @@ -112,16 +116,32 @@ export async function runFullLifecycle({ rewardUnit: "DEMO_CREDIT" }); - const maintainerApi = roleClient(baseUrl, tokens.maintainer); - const contributorApi = roleClient(baseUrl, tokens.contributor); - const verifierApi = roleClient(baseUrl, tokens.verifier); + const evidence = await fetchVerifiedPullRequest({ + repository: bounty.repository, + prNumber: Number(prNumber), + token: githubToken, + expected: { + baseBranch, + contributorGithub, + issueNumber: bounty.issueNumber + } + }); + + const maintainerApi = roleClient(baseUrl, tokens.maintainer, runtime); + const contributorApi = roleClient(baseUrl, tokens.contributor, runtime); + const verifierApi = roleClient(baseUrl, tokens.verifier, runtime); const proof = { + schemaVersion: 2, + runId, + packageId, + generatedAt: now().toISOString(), + environment: runtime.insecureLocal ? "local-sandbox-no-auth" : "authenticated-ledger", issue, bounty, parties, steps: [], negativeChecks: [], - mergeEvidence: null, + mergeEvidence: evidence, settlementReceipt: null }; @@ -173,18 +193,6 @@ export async function runFullLifecycle({ }); proof.steps.push(proofStep("CLAIMED", claimAccepted.submission, claimAccepted.event)); - const evidence = await fetchVerifiedPullRequest({ - repository: bounty.repository, - prNumber: Number(prNumber), - token: githubToken, - expected: { - baseBranch, - contributorGithub, - issueNumber: bounty.issueNumber - } - }); - proof.mergeEvidence = evidence; - const pullRequest = { repository: evidence.repository, prNumber: evidence.prNumber, @@ -219,7 +227,8 @@ export async function runFullLifecycle({ actAs: [parties.verifier], workflowId: "commitledger-negative-wrong-issue", commandId: `negative-wrong-issue-${Date.now()}` - }) + }), + { codes: ["DAML_UNHANDLED_EXCEPTION", "DAML_INTERPRETATION_ERROR"], contains: "evidence issue number mismatch" } )); const verified = await submitAndFind({ @@ -251,7 +260,8 @@ export async function runFullLifecycle({ actAs: [parties.contributor], workflowId: "commitledger-negative-unauthorized-settle", commandId: `negative-unauthorized-settle-${Date.now()}` - }) + }), + { codes: ["DAML_AUTHORIZATION_ERROR"] } )); const settled = await submitAndFind({ @@ -283,7 +293,8 @@ export async function runFullLifecycle({ actAs: [parties.maintainer], workflowId: "commitledger-negative-duplicate-settle", commandId: `negative-duplicate-settle-${Date.now()}` - }) + }), + { codes: ["CONTRACT_NOT_FOUND", "CONTRACT_NOT_ACTIVE"], contains: verified.event.contractId } )); return proof; diff --git a/src/run-full-demo.mjs b/src/run-full-demo.mjs index 6f584d7..51c84d4 100644 --- a/src/run-full-demo.mjs +++ b/src/run-full-demo.mjs @@ -1,31 +1,20 @@ -import { writeFile } from "node:fs/promises"; -import { runtimeConfigFromEnv, runFullLifecycle } from "./orchestrator.mjs"; - -const issueUrl = process.env.COMMITLEDGER_ISSUE_URL || "https://github.com/Saidur-droid/CommitLedger/issues/5"; -const prNumber = Number(process.env.COMMITLEDGER_PR_NUMBER || "6"); -const contributorGithub = process.env.COMMITLEDGER_CONTRIBUTOR_GITHUB || "Saidur-droid"; -const rewardAmount = Number(process.env.COMMITLEDGER_REWARD_AMOUNT || "100"); -const baseBranch = process.env.COMMITLEDGER_BASE_BRANCH || "main"; - +import {writeFile} from 'node:fs/promises'; +import {runtimeConfigFromEnv,runFullLifecycle} from './orchestrator.mjs'; try { - const proof = await runFullLifecycle({ - runtime: runtimeConfigFromEnv(), - issueUrl, - rewardAmount, - contributorGithub, - prNumber, - baseBranch, - githubToken: process.env.GITHUB_TOKEN || "" + const proof=await runFullLifecycle({ + runtime:runtimeConfigFromEnv(), + issueUrl:process.env.COMMITLEDGER_ISSUE_URL || 'https://github.com/Saidur-droid/CommitLedger/issues/5', + prNumber:Number(process.env.COMMITLEDGER_PR_NUMBER || 6), + contributorGithub:process.env.COMMITLEDGER_CONTRIBUTOR_GITHUB || 'Saidur-droid', + rewardAmount:Number(process.env.COMMITLEDGER_REWARD_AMOUNT || 100), + baseBranch:process.env.COMMITLEDGER_BASE_BRANCH || 'main', + githubToken:process.env.GITHUB_TOKEN || '' }); - - const json = JSON.stringify(proof, null, 2); + proof.sourceCommit=process.env.GITHUB_SHA || 'local-working-tree'; + const json=JSON.stringify(proof,null,2)+'\n'; console.log(json); - - if (process.env.COMMITLEDGER_EVIDENCE_FILE) { - await writeFile(process.env.COMMITLEDGER_EVIDENCE_FILE, json + "\n", "utf8"); - console.error(`Evidence written to ${process.env.COMMITLEDGER_EVIDENCE_FILE}`); - } -} catch (error) { + if(process.env.COMMITLEDGER_EVIDENCE_FILE) await writeFile(process.env.COMMITLEDGER_EVIDENCE_FILE,json); +} catch(error) { console.error(`CommitLedger full demo failed: ${error.message}`); - process.exit(1); + process.exitCode=1; } diff --git a/src/server.mjs b/src/server.mjs index b8e4388..9c28350 100644 --- a/src/server.mjs +++ b/src/server.mjs @@ -1,204 +1,89 @@ -import http from "node:http"; -import { readFile } from "node:fs/promises"; -import { extname, join, normalize } from "node:path"; -import { fileURLToPath } from "node:url"; -import { fetchGitHubIssue, fetchVerifiedPullRequest } from "./github-verifier.mjs"; -import { validateBountyDraft } from "./domain.mjs"; -import { - buildCreateBountyCommand, - buildClaimRequestCommand, - buildAcceptClaimCommand, - buildSubmitPullRequestCommand, - buildReturnForRevisionCommand, - buildVerifyMergedCommand, - buildSettleCommand -} from "./canton-commands.mjs"; -import { CantonJsonApi } from "./canton-json-api.mjs"; -import { runtimeConfigFromEnv, runFullLifecycle } from "./orchestrator.mjs"; - -const root = fileURLToPath(new URL("../web/", import.meta.url)); -const port = Number(process.env.PORT || 4173); - -function json(res, status, payload) { - const body = JSON.stringify(payload, null, 2); - res.writeHead(status, { - "Content-Type": "application/json; charset=utf-8", - "Content-Length": Buffer.byteLength(body), - "Cache-Control": "no-store" - }); - res.end(body); +import http from 'node:http'; +import {readFile} from 'node:fs/promises'; +import {extname,resolve,sep} from 'node:path'; +import {fileURLToPath} from 'node:url'; +import {fetchGitHubIssue,fetchVerifiedPullRequest} from './github-verifier.mjs'; +import {validateBountyDraft} from './domain.mjs'; +import * as builders from './canton-commands.mjs'; +import {CantonJsonApi} from './canton-json-api.mjs'; +import {runtimeConfigFromEnv,runFullLifecycle} from './orchestrator.mjs'; +const root=fileURLToPath(new URL('../web/',import.meta.url)); +const actions=Object.freeze({createBounty:'buildCreateBountyCommand',claimRequest:'buildClaimRequestCommand',acceptClaim:'buildAcceptClaimCommand',submitPullRequest:'buildSubmitPullRequestCommand',returnForRevision:'buildReturnForRevisionCommand',verifyMerged:'buildVerifyMergedCommand',settle:'buildSettleCommand'}); +function json(res,status,payload) { + res.writeHead(status,{'Content-Type':'application/json; charset=utf-8','Cache-Control':'no-store','X-Content-Type-Options':'nosniff'}); + res.end(JSON.stringify(payload,null,2)); } - async function readJson(req) { - let body = ""; - for await (const chunk of req) { - body += chunk; - if (body.length > 1_000_000) throw new Error("request body too large"); - } - return body ? JSON.parse(body) : {}; -} - -function contentType(path) { - return ({ - ".html": "text/html; charset=utf-8", - ".css": "text/css; charset=utf-8", - ".js": "text/javascript; charset=utf-8", - ".svg": "image/svg+xml" - })[extname(path)] || "application/octet-stream"; -} - -async function serveStatic(urlPath, res) { - const clean = normalize(urlPath === "/" ? "/index.html" : urlPath).replace(/^([.][.][/\\])+/, ""); - const target = join(root, clean); - if (!target.startsWith(root)) return json(res, 403, { error: "forbidden" }); - try { - const body = await readFile(target); - res.writeHead(200, { "Content-Type": contentType(target), "Cache-Control": "no-store" }); - res.end(body); - } catch { - json(res, 404, { error: "not found" }); - } -} - -function cantonConfigured() { - return Boolean(process.env.CANTON_JSON_API_URL && (process.env.CANTON_TOKEN || process.env.CANTON_MAINTAINER_TOKEN)); -} - -function lifecycleConfigured() { - try { - runtimeConfigFromEnv(); - return true; - } catch { - return false; - } -} - -function cantonApi() { - const token = process.env.CANTON_TOKEN || process.env.CANTON_MAINTAINER_TOKEN; - if (!process.env.CANTON_JSON_API_URL || !token) return null; - return new CantonJsonApi({ baseUrl: process.env.CANTON_JSON_API_URL, token }); + let length=0;const chunks=[]; + for await(const chunk of req) {length+=chunk.length;if(length>1_000_000) throw new Error('request body too large');chunks.push(chunk);} + const value=JSON.parse(Buffer.concat(chunks).toString('utf8')||'{}'); + if(!value || typeof value!=='object' || Array.isArray(value)) throw new Error('invalid JSON object'); + return value; } - -async function submitCanton(command, actAs, workflowId) { - const api = cantonApi(); - if (!api) throw new Error("Canton LocalNet is not configured"); - return api.submitAndWait({ - commands: [command], - actAs, - workflowId, - commandId: `commitledger-${workflowId}-${Date.now()}` - }); -} - -const server = http.createServer(async (req, res) => { - try { - const url = new URL(req.url, `http://${req.headers.host || "localhost"}`); - - if (req.method === "GET" && url.pathname === "/api/health") { - return json(res, 200, { - ok: true, - service: "CommitLedger", - cantonConfigured: cantonConfigured(), - lifecycleConfigured: lifecycleConfigured(), - githubAuthenticated: Boolean(process.env.GITHUB_TOKEN), - packageConfigured: Boolean(process.env.CANTON_PACKAGE_ID) - }); - } - - if (req.method === "POST" && url.pathname === "/api/bounty/prepare") { - const input = await readJson(req); - const issue = await fetchGitHubIssue({ issueUrl: input.issueUrl, token: process.env.GITHUB_TOKEN }); - const bounty = validateBountyDraft({ - repository: issue.repository, - issueNumber: issue.issueNumber, - issueUrl: issue.issueUrl, - title: input.title || issue.title, - rewardAmount: input.rewardAmount, - rewardUnit: "DEMO_CREDIT" - }); - return json(res, 200, { ok: true, issue, bounty }); - } - - if (req.method === "POST" && url.pathname === "/api/github/verify") { - const input = await readJson(req); - const evidence = await fetchVerifiedPullRequest({ - repository: input.repository, - prNumber: Number(input.prNumber), - token: process.env.GITHUB_TOKEN, - expected: { - headSha: input.headSha || "", - baseBranch: input.baseBranch || "", - contributorGithub: input.contributorGithub || "", - issueNumber: Number(input.issueNumber) +export function createAppServer(env=process.env) { + return http.createServer(async(req,res)=>{ + try { + const origin=`http://${req.headers.host||'localhost'}`; + const url=new URL(req.url,origin); + if(!['127.0.0.1','localhost','[::1]'].includes(url.hostname)) return json(res,403,{error:'Loopback host required'}); + if(req.method==='POST') { + if(req.headers.origin && req.headers.origin!==origin) return json(res,403,{error:'Cross-origin write rejected'}); + if(!/^application\/json(?:;|$)/i.test(req.headers['content-type']||'')) return json(res,415,{error:'application/json is required'}); + } + if(req.method==='GET' && url.pathname==='/api/health') { + let runtime;let configurationError='';let ledgerReachable=false; + try {runtime=runtimeConfigFromEnv(env);} catch(error) {configurationError=error.message;} + if(runtime) { + try { + const api=new CantonJsonApi({baseUrl:runtime.baseUrl,token:runtime.tokens.maintainer,userId:runtime.userId,insecureLocal:runtime.insecureLocal}); + await api.request('/v2/state/ledger-end',{signal:AbortSignal.timeout(3000)}); + ledgerReachable=true; + } catch { /* Connectivity is separate from configuration and ledger proof. */ } } - }); - return json(res, 200, { ok: true, evidence }); - } - - if (req.method === "POST" && url.pathname === "/api/demo/run") { - if (!lifecycleConfigured()) { - return json(res, 503, { error: "Full Canton lifecycle is not configured. Set package, party and token environment variables." }); + return json(res,200,{ok:true,service:'CommitLedger',lifecycleConfigured:Boolean(runtime),cantonConfigured:Boolean(runtime),ledgerReachable,configurationError,githubAuthenticated:Boolean(env.GITHUB_TOKEN),packageConfigured:Boolean(env.CANTON_PACKAGE_ID)}); } - const input = await readJson(req); - const proof = await runFullLifecycle({ - runtime: runtimeConfigFromEnv(), - issueUrl: input.issueUrl, - rewardAmount: Number(input.rewardAmount || 100), - contributorGithub: input.contributorGithub, - prNumber: Number(input.prNumber), - baseBranch: input.baseBranch || "main", - githubToken: process.env.GITHUB_TOKEN || "" - }); - return json(res, 200, { ok: true, proof }); - } - - if (req.method === "POST" && url.pathname === "/api/canton/build-command") { - const input = await readJson(req); - const packageId = input.packageId || process.env.CANTON_PACKAGE_ID; - if (!packageId) throw new Error("CANTON_PACKAGE_ID is required"); - let command; - switch (input.action) { - case "createBounty": - command = buildCreateBountyCommand({ packageId, ...input.payload }); - break; - case "claimRequest": - command = buildClaimRequestCommand({ packageId, ...input.payload }); - break; - case "acceptClaim": - command = buildAcceptClaimCommand({ packageId, ...input.payload }); - break; - case "submitPullRequest": - command = buildSubmitPullRequestCommand({ packageId, ...input.payload }); - break; - case "returnForRevision": - command = buildReturnForRevisionCommand({ packageId, ...input.payload }); - break; - case "verifyMerged": - command = buildVerifyMergedCommand({ packageId, ...input.payload }); - break; - case "settle": - command = buildSettleCommand({ packageId, ...input.payload }); - break; - default: - throw new Error("Unknown Canton action"); + if(req.method==='POST' && url.pathname==='/api/canton/submit-command') { + return json(res,403,{error:'Arbitrary browser-supplied ledger commands are disabled; use the verified lifecycle'}); } - return json(res, 200, { ok: true, command }); - } - - if (req.method === "POST" && url.pathname === "/api/canton/submit-command") { - const input = await readJson(req); - const result = await submitCanton(input.command, input.actAs, input.workflowId || "web"); - return json(res, 200, { ok: true, result }); + if(req.method==='POST' && url.pathname==='/api/bounty/prepare') { + const input=await readJson(req); + const issue=await fetchGitHubIssue({issueUrl:input.issueUrl,token:env.GITHUB_TOKEN}); + const bounty=validateBountyDraft({...issue,rewardAmount:input.rewardAmount,rewardUnit:'DEMO_CREDIT'}); + return json(res,200,{ok:true,issue,bounty}); + } + if(req.method==='POST' && url.pathname==='/api/github/verify') { + const input=await readJson(req); + const evidence=await fetchVerifiedPullRequest({repository:input.repository,prNumber:Number(input.prNumber),token:env.GITHUB_TOKEN,expected:{headSha:input.headSha||'',mergeCommitSha:input.mergeCommitSha||'',baseBranch:input.baseBranch||'main',contributorGithub:input.contributorGithub||'',issueNumber:Number(input.issueNumber)}}); + return json(res,200,{ok:true,evidence}); + } + if(req.method==='POST' && url.pathname==='/api/demo/run') { + let runtime; + try {runtime=runtimeConfigFromEnv(env);} catch(error) {return json(res,503,{error:`Canton setup pending: ${error.message}`});} + const input=await readJson(req); + const proof=await runFullLifecycle({runtime,issueUrl:input.issueUrl,rewardAmount:Number(input.rewardAmount||100),contributorGithub:input.contributorGithub,prNumber:Number(input.prNumber),baseBranch:input.baseBranch||'main',githubToken:env.GITHUB_TOKEN||''}); + return json(res,200,{ok:true,proof}); + } + if(req.method==='POST' && url.pathname==='/api/canton/build-command') { + const input=await readJson(req); + const builder=Object.hasOwn(actions,input.action) ? builders[actions[input.action]] : null; + if(typeof builder!=='function') return json(res,400,{error:'Unknown Canton action'}); + const command=builder({...input.payload,packageId:env.CANTON_PACKAGE_ID||input.packageId}); + return json(res,200,{ok:true,command}); + } + if(req.method!=='GET') return json(res,405,{error:'method not allowed'}); + const target=resolve(root,'.'+(url.pathname==='/'?'/index.html':url.pathname)); + if(!target.startsWith(resolve(root)+sep)) return json(res,403,{error:'forbidden'}); + try { + const body=await readFile(target); + res.writeHead(200,{'Content-Type':({'.html':'text/html; charset=utf-8','.css':'text/css; charset=utf-8','.js':'text/javascript; charset=utf-8','.svg':'image/svg+xml'})[extname(target)]||'application/octet-stream','Cache-Control':'no-store','X-Content-Type-Options':'nosniff'}); + res.end(body); + } catch {json(res,404,{error:'not found'});} + } catch(error) { + json(res,/required|invalid|mismatch|must|Unknown|distinct|JSON|too large/.test(error.message)?400:502,{error:error.message}); } - - if (req.method === "GET") return serveStatic(url.pathname, res); - json(res, 405, { error: "method not allowed" }); - } catch (error) { - const status = /not configured|required|invalid|mismatch|must|Unknown|distinct/.test(error.message) ? 400 : 500; - json(res, status, { error: error.message }); - } -}); - -server.listen(port, "127.0.0.1", () => { - console.log(`CommitLedger running at http://127.0.0.1:${port}`); -}); + }); +} +if(process.argv[1] && resolve(process.argv[1])===fileURLToPath(import.meta.url)) { + const port=Number(process.env.PORT||4173); + createAppServer().listen(port,'127.0.0.1',()=>console.log(`CommitLedger running at http://127.0.0.1:${port}`)); +} diff --git a/tests/github-verifier.test.mjs b/tests/github-verifier.test.mjs index 0b9c92d..a0db588 100644 --- a/tests/github-verifier.test.mjs +++ b/tests/github-verifier.test.mjs @@ -13,9 +13,10 @@ const payload = { html_url: "https://github.com/Saidur-droid/CommitLedger/pull/6", body: "Competition/demo evidence for #5.", merged: true, + merge_commit_sha: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", merged_at: "2026-09-27T10:57:30Z", user: { login: "Saidur-droid" }, - head: { sha: "abc123" }, + head: { sha: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }, base: { ref: "main", repo: { full_name: "Saidur-droid/CommitLedger" } } }; @@ -51,7 +52,7 @@ test("binds the exact bounty issue into canonical merge evidence", () => { assertExpectedPullRequest(pr, { repository: "saidur-droid/commitledger", prNumber: 6, - headSha: "abc123", + headSha: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", baseBranch: "main", contributorGithub: "Saidur-droid", issueNumber: 5 diff --git a/tests/lifecycle-transport.test.mjs b/tests/lifecycle-transport.test.mjs new file mode 100644 index 0000000..f973d8d --- /dev/null +++ b/tests/lifecycle-transport.test.mjs @@ -0,0 +1,91 @@ +// Simulated HTTP transport tests. These are NOT Canton runtime evidence. +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {runFullLifecycle} from '../src/orchestrator.mjs'; +const repository='Saidur-droid/CommitLedger'; +const parties={maintainer:'Maintainer::unit',contributor:'Contributor::unit',verifier:'Verifier::unit'}; +const runtime={baseUrl:'http://localhost:3975',packageId:'unit-package',parties,tokens:{maintainer:'unit-maintainer',contributor:'unit-contributor',verifier:'unit-verifier'}}; +const input={runtime,issueUrl:`https://github.com/${repository}/issues/5`,contributorGithub:'Saidur-droid',prNumber:6}; +function installTransport(t, {unmerged=false,negativeNetworkFailure=false}={}) { + const active=new Map(); const commands=[]; let seq=0; + t.mock.method(globalThis,'fetch',async (url,options={}) => { + if(url.startsWith('https://api.github.com/')) { + if(url.includes('/issues/')) return Response.json({number:5,html_url:input.issueUrl,title:'Unit fixture',state:'open',user:{login:'Saidur-droid'}}); + if(url.includes('/pulls/')) return Response.json({number:6,html_url:`https://github.com/${repository}/pull/6`,body:'References #5',merged:!unmerged,merged_at:unmerged?null:'2026-09-27T10:57:30Z',merge_commit_sha:'b'.repeat(40),head:{sha:'a'.repeat(40)},base:{ref:'main',repo:{full_name:repository}},user:{login:'Saidur-droid'}}); + if(url.includes('/commits/')) return Response.json({sha:'b'.repeat(40)}); + if(url.includes('/compare/')) return Response.json({status:'ahead'}); + throw new Error(`Unexpected GitHub URL: ${url}`); + } + const body=JSON.parse(options.body); + if(url.endsWith('/v2/state/active-contracts')) { + return Response.json([...active.values()].map(createdEvent=>({contractEntry:{JsActiveContract:{createdEvent}}}))); + } + assert.ok(url.endsWith('/v2/commands/submit-and-wait')); + commands.push(body); + const command=body.commands[0]; let name,args; + if(command.CreateCommand) { + name=command.CreateCommand.templateId.split(':').at(-1); + args=command.CreateCommand.createArguments; + } else { + const ex=command.ExerciseCommand; + const previous=active.get(ex.contractId); + if(!previous) return Response.json({code:'CONTRACT_NOT_FOUND',cause:`Contract ${ex.contractId} already consumed`},{status:404}); + args={...previous.createArgument}; + if(ex.choice==='ClaimRequest_Accept') { + assert.equal(body.actAs[0],parties.maintainer); + const bounty=active.get(ex.choiceArgument.bountyCid); + assert.ok(bounty,'exact prior bounty CID must be carried forward'); + args={...bounty.createArgument,contributor:args.contributor,contributorGithub:args.contributorGithub}; + active.delete(ex.choiceArgument.bountyCid); name='ClaimedBounty'; + } else if(ex.choice==='ClaimedBounty_SubmitPullRequest') { + assert.equal(body.actAs[0],parties.contributor); + args.pullRequest=ex.choiceArgument.pullRequest; name='SubmittedBounty'; + } else if(ex.choice==='SubmittedBounty_VerifyMerged') { + assert.equal(body.actAs[0],parties.verifier); + if(ex.choiceArgument.evidence.issueNumber!==args.issueNumber) { + if(negativeNetworkFailure) throw new TypeError('fetch failed'); + return Response.json({code:'DAML_UNHANDLED_EXCEPTION',cause:'evidence issue number mismatch'},{status:400}); + } + args.evidence=ex.choiceArgument.evidence;name='VerifiedBounty'; + } else if(ex.choice==='VerifiedBounty_Settle') { + if(body.actAs[0]!==parties.maintainer) return Response.json({code:'DAML_AUTHORIZATION_ERROR',cause:'wrong controller'},{status:400}); + args={...args,...ex.choiceArgument,evidenceHash:args.evidence.evidenceHash,mergeCommitSha:args.evidence.mergeCommitSha};name='SettlementReceipt'; + } else throw new Error(`Unexpected choice ${ex.choice}`); + active.delete(ex.contractId); + } + seq++; + const event={contractId:`unit-contract-${seq}`,templateId:`unit-package:CommitLedger:${name}`,createArgument:args}; + active.set(event.contractId,event); + return Response.json({updateId:`unit-update-${seq}`,completionOffset:seq}); + }); + return {commands,active}; +} +test('UNIT TRANSPORT: six transitions chain exact contract IDs and capture three specific rejections',async t=>{ + const {commands,active}=installTransport(t); + const proof=await runFullLifecycle(input); + assert.equal(proof.steps.length,6); + assert.equal(proof.negativeChecks.length,3); + assert.equal(commands.length,9); + assert.equal(active.size,1); + assert.equal(proof.settlementReceipt.evidenceHash,proof.mergeEvidence.evidenceHash); + assert.equal(proof.settlementReceipt.mergeCommitSha,'b'.repeat(40)); + assert.deepEqual(proof.steps.map(x=>x.contractId),Array.from({length:6},(_,i)=>`unit-contract-${i+1}`)); + assert.deepEqual(proof.negativeChecks.map(x=>x.code),['DAML_UNHANDLED_EXCEPTION','DAML_AUTHORIZATION_ERROR','CONTRACT_NOT_FOUND']); +}); +test('UNIT TRANSPORT: invalid PR is rejected before ANY ledger mutation',async t=>{ + const {commands}=installTransport(t,{unmerged:true}); + await assert.rejects(runFullLifecycle(input),/not merged/); + assert.equal(commands.length,0); +}); +test('UNIT TRANSPORT: a network failure during a negative check aborts, never becomes proof',async t=>{ + const {commands}=installTransport(t,{negativeNetworkFailure:true}); + await assert.rejects(runFullLifecycle(input),/Unproven ledger rejection/); + assert.equal(commands.length,5); +}); +test('UNIT TRANSPORT: repeat demos use distinct bounty identities',async t=>{ + installTransport(t); + const a=await runFullLifecycle(input); + const b=await runFullLifecycle(input); + assert.notEqual(a.bounty.bountyId,b.bounty.bountyId); + assert.notEqual(a.runId,b.runId); +}); diff --git a/tests/security-regressions.test.mjs b/tests/security-regressions.test.mjs new file mode 100644 index 0000000..3495f9d --- /dev/null +++ b/tests/security-regressions.test.mjs @@ -0,0 +1,118 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { referencesIssue, normalizePullRequest, assertExpectedPullRequest, buildMergeEvidence, fetchVerifiedPullRequest, parseGitHubIssueUrl } from '../src/github-verifier.mjs'; +import { CantonApiError, expectLedgerFailure } from '../src/ledger-errors.mjs'; +import { CantonJsonApi } from '../src/canton-json-api.mjs'; +import { runtimeConfigFromEnv } from '../src/orchestrator.mjs'; + +const repository = 'Saidur-droid/CommitLedger'; +const head = 'a'.repeat(40); +const merge = 'b'.repeat(40); +const payload = { + number: 6, html_url: `https://github.com/${repository}/pull/6`, body: 'References #5.', + merged: true, merged_at: '2026-09-27T10:57:30Z', merge_commit_sha: merge, + user: {login: 'Saidur-droid'}, head: {sha: head}, base: {ref: 'main', repo: {full_name: repository}} +}; +for (const body of [ + '#5', 'Refs #5.', '(#5)', 'References Saidur-droid/CommitLedger#5', + `https://github.com/${repository}/issues/5`, `[issue](https://github.com/${repository}/issues/5)`, + 'Refs saidur-droid/commitledger#5' +]) test(`accepts explicit matching issue reference: ${body}`, () => { + assert.equal(referencesIssue(body, repository, 5), true); +}); +for (const body of [ + 'another/repository#5', 'https://github.com/another/repository/issues/5', + 'https://example.com/issues/5', 'https://example.com/#5', '#50', '#5foo', + '[#5](https://github.com/another/repository/issues/5)', '`#5`', '', + '\\#5', 'issue #500', 'https://github.com.evil.example/Saidur-droid/CommitLedger/issues/5', + 'evil/Saidur-droid/CommitLedger#5', 'http://github.com/Saidur-droid/CommitLedger/issues/5' +]) test(`rejects misleading issue reference: ${body}`, () => { + assert.equal(referencesIssue(body, repository, 5), false); +}); +test('rejects issue URLs with unsafe schemes or identity mismatch', () => { + for (const url of ['http://github.com/a/b/issues/5', 'https://user@github.com/a/b/issues/5', 'https://github.com/a/b/issues/9007199254740992']) { + assert.throws(() => parseGitHubIssueUrl(url)); + } +}); +test('merge commit SHA is required and included in the hash', () => { + const expected = { repository, prNumber: 6, issueNumber: 5 }; + for (const value of [null, '', 'abc', 'z'.repeat(40)]) { + assert.throws(() => assertExpectedPullRequest(normalizePullRequest({...payload, merge_commit_sha: value}), expected), /merge commit SHA/); + } + const a = buildMergeEvidence(assertExpectedPullRequest(normalizePullRequest(payload), expected)); + const b = buildMergeEvidence(assertExpectedPullRequest(normalizePullRequest({...payload, merge_commit_sha: 'c'.repeat(40)}), expected)); + assert.equal(a.mergeCommitSha, merge); + assert.notEqual(a.evidenceHash, b.evidenceHash); + assert.throws(() => assertExpectedPullRequest(normalizePullRequest(payload), {...expected, mergeCommitSha: 'd'.repeat(40)}), /merge commit SHA mismatch/); +}); +test('canonical merge commit and ancestry are verified server-side', async t => { + const urls = []; + t.mock.method(globalThis, 'fetch', async url => { + urls.push(url); + const value = url.includes('/pulls/') ? payload : url.includes('/commits/') ? {sha:merge} : {status:'ahead'}; + return Response.json(value); + }); + const evidence = await fetchVerifiedPullRequest({repository, prNumber:6, expected:{issueNumber:5, baseBranch:'main'}}); + assert.equal(evidence.mergeCommitSha, merge); + assert.equal(urls.length, 3); + assert.match(urls[1], /\/commits\/b{40}$/); + assert.match(urls[2], /\/compare\/b{40}\.\.\.main$/); +}); +test('a diverged base branch does not produce merge evidence', async t => { + t.mock.method(globalThis, 'fetch', async url => Response.json(url.includes('/pulls/') ? payload : url.includes('/commits/') ? {sha:merge} : {status:'diverged'})); + await assert.rejects(fetchVerifiedPullRequest({repository, prNumber:6, expected:{issueNumber:5}}), /not reachable/); +}); +test('a wrong canonical merge commit is rejected', async t => { + t.mock.method(globalThis, 'fetch', async url => Response.json(url.includes('/pulls/') ? payload : {sha:'c'.repeat(40)})); + await assert.rejects(fetchVerifiedPullRequest({repository, prNumber:6, expected:{issueNumber:5}}), /canonical merge commit SHA mismatch/); +}); +const expected = {codes:['DAML_AUTHORIZATION_ERROR']}; +for (const error of [ + new TypeError('fetch failed'), new Error('timeout'), + new CantonApiError(503, {code:'DAML_AUTHORIZATION_ERROR',cause:'server unavailable'}), + new CantonApiError(401, {code:'DAML_AUTHORIZATION_ERROR',cause:'expired JWT'}), + new CantonApiError(403, {code:'DAML_AUTHORIZATION_ERROR',cause:'token lacks rights'}), + new CantonApiError(429, {code:'DAML_AUTHORIZATION_ERROR',cause:'rate limit'}), + new CantonApiError(400, {code:'INVALID_ARGUMENT',cause:'wrong command schema'}), + new CantonApiError(400, {raw:'DAML_AUTHORIZATION_ERROR'}) +]) test(`does not treat infrastructure/configuration failure as authorization proof: ${error.message}`, async () => { + await assert.rejects(expectLedgerFailure('unauthorized', async () => {throw error;}, expected), /Unproven ledger rejection/); +}); +test('only an explicit ledger authorization error proves rejection', async () => { + const result = await expectLedgerFailure('unauthorized', async () => { + throw new CantonApiError(400, {code:'DAML_AUTHORIZATION_ERROR',cause:'Wrong controller'}); + }, expected); + assert.equal(result.rejected, true); + assert.equal(result.code, 'DAML_AUTHORIZATION_ERROR'); +}); +test('an unexpectedly successful negative check fails the run', async () => { + await assert.rejects(expectLedgerFailure('unauthorized', async () => ({}), expected), /did not occur/); +}); +test('wrong-issue rejection must name the issue mismatch, not another assertion', async () => { + await assert.rejects(expectLedgerFailure('wrong issue', async () => { + throw new CantonApiError(400, {code:'DAML_UNHANDLED_EXCEPTION',cause:'different failed assertion'}); + }, {codes:['DAML_UNHANDLED_EXCEPTION'],contains:'evidence issue number mismatch'}), /Unproven/); +}); +test('Canton client preserves structured error details', async t => { + t.mock.method(globalThis, 'fetch', async () => Response.json({code:'DAML_AUTHORIZATION_ERROR',cause:'wrong role'}, {status:400})); + const api = new CantonJsonApi({baseUrl:'http://localhost:3975',token:'unit-token'}); + await assert.rejects(api.request('/unit-test'), e => e instanceof CantonApiError && e.code === 'DAML_AUTHORIZATION_ERROR' && e.status === 400); +}); +test('Canton commands use v2 userId and synchronizerId names', async t => { + let body; + t.mock.method(globalThis, 'fetch', async (_url, options) => {body=JSON.parse(options.body);return Response.json({});}); + await new CantonJsonApi({baseUrl:'http://localhost:3975',token:'unit-token'}).submitAndWait({commands:[],actAs:[],commandId:'unit'}); + assert.ok('userId' in body && 'synchronizerId' in body); + assert.equal('applicationId' in body, false); + assert.equal('domainId' in body, false); +}); +test('insecure sandbox configuration cannot target a remote server', () => { + assert.throws(() => new CantonJsonApi({baseUrl:'https://remote.example',insecureLocal:true}), /loopback/); + assert.throws(() => runtimeConfigFromEnv({CANTON_JSON_API_URL:'https://remote.example',CANTON_INSECURE_LOCAL:'true'}), /loopback/); +}); +test('ambiguous active contracts cannot silently choose an old demo receipt', async t => { + const entry = {contractEntry:{JsActiveContract:{createdEvent:{contractId:'unit-cid',templateId:'pkg:CommitLedger:Bounty',createArgument:{}}}}}; + t.mock.method(globalThis, 'fetch', async () => Response.json([entry,entry])); + const api=new CantonJsonApi({baseUrl:'http://localhost:3975',token:'unit-token'}); + await assert.rejects(api.findActiveContract({party:'unit-party',templateId:'pkg:CommitLedger:Bounty',activeAtOffset:1}), /Ambiguous/); +}); diff --git a/tests/server.test.mjs b/tests/server.test.mjs new file mode 100644 index 0000000..6fe4a96 --- /dev/null +++ b/tests/server.test.mjs @@ -0,0 +1,28 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {createAppServer} from '../src/server.mjs'; +async function withServer(t) { + const server=createAppServer({}); + await new Promise(resolve=>server.listen(0,'127.0.0.1',resolve)); + t.after(()=>new Promise(resolve=>{server.close(resolve);server.closeAllConnections();})); + return `http://127.0.0.1:${server.address().port}`; +} +test('health never calls unconfigured Canton ready',async t=>{ + const base=await withServer(t);const value=await (await fetch(base+'/api/health')).json(); + assert.equal(value.lifecycleConfigured,false);assert.equal(value.ledgerReachable,false); +}); +test('unconfigured demo fails closed',async t=>{ + const base=await withServer(t);const response=await fetch(base+'/api/demo/run',{method:'POST',headers:{'Content-Type':'application/json'},body:'{}'}); + assert.equal(response.status,503); +}); +test('arbitrary browser commands cannot use server-held ledger credentials',async t=>{ + const base=await withServer(t);const response=await fetch(base+'/api/canton/submit-command',{method:'POST',headers:{'Content-Type':'application/json'},body:'{}'}); + assert.equal(response.status,403); +}); +test('cross-origin writes and simple-form content types are rejected',async t=>{ + const base=await withServer(t); + const a=await fetch(base+'/api/demo/run',{method:'POST',headers:{'Content-Type':'application/json',Origin:'https://evil.example'},body:'{}'}); + assert.equal(a.status,403); + const b=await fetch(base+'/api/demo/run',{method:'POST',headers:{'Content-Type':'text/plain'},body:'{}'}); + assert.equal(b.status,415); +}); diff --git a/web/app.js b/web/app.js index b009518..6e35036 100644 --- a/web/app.js +++ b/web/app.js @@ -1,156 +1,49 @@ -const bountyForm = document.querySelector("#bounty-form"); -const verifyForm = document.querySelector("#verify-form"); -const liveForm = document.querySelector("#live-form"); -const liveRun = document.querySelector("#live-run"); -const liveStatus = document.querySelector("#live-status"); -const timeline = document.querySelector("#timeline"); -const receiptCard = document.querySelector("#receipt-card"); -const issueStatus = document.querySelector("#issue-status"); -const githubStatus = document.querySelector("#github-status"); -const cantonBadge = document.querySelector("#canton-badge"); -const bountySummary = document.querySelector("#bounty-summary"); -const githubOutput = document.querySelector("#github-output"); - -let health = {}; - -async function request(path, options = {}) { - const response = await fetch(path, { - ...options, - headers: { "Content-Type": "application/json", ...(options.headers || {}) } - }); - const data = await response.json(); - if (!response.ok) throw new Error(data.error || "Request failed"); - return data; +const $=selector=>document.querySelector(selector); +let ready=false,lastProof=null; +const escape=value=>String(value??'').replace(/[&<>"']/g,char=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[char])); +async function request(path,body) { + const response=await fetch(path,body?{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}:{}); + const value=await response.json(); + if(!response.ok) throw new Error(value.error||'Request failed'); + return value; } - -async function checkHealth() { +async function health() { try { - health = await request("/api/health"); - cantonBadge.textContent = health.lifecycleConfigured ? "Canton lifecycle ready" : "Canton setup pending"; - liveStatus.textContent = health.lifecycleConfigured ? "Ready for real ledger run" : "Runtime not configured"; - liveRun.disabled = !health.lifecycleConfigured; - } catch { - cantonBadge.textContent = "Local app offline"; - liveStatus.textContent = "Offline"; - liveRun.disabled = true; - } + const h=await request('/api/health');ready=h.lifecycleConfigured && h.ledgerReachable; + $('#canton-badge').textContent=ready?'Canton connected':'Canton setup pending'; + $('#live-status').textContent=ready?'Ready for a real ledger run':h.lifecycleConfigured?'Ledger unreachable':'Runtime not configured'; + $('#configuration-note').textContent=h.configurationError||(!h.ledgerReachable?'Check that your Canton development ledger is running.':'Connected. No settlement has been claimed until a receipt is returned.'); + } catch(error) {ready=false;$('#canton-badge').textContent='Local app offline';$('#configuration-note').textContent=error.message;} + $('#live-run').disabled=!ready; } - -liveForm.addEventListener("submit", async (event) => { - event.preventDefault(); - if (!health.lifecycleConfigured) return; - liveRun.disabled = true; - liveStatus.textContent = "Submitting to Canton…"; - timeline.innerHTML = '
Executing real ledger transitions. No mocked steps are shown.
'; - receiptCard.classList.add("hidden"); - try { - const input = Object.fromEntries(new FormData(liveForm)); - input.prNumber = Number(input.prNumber); - input.rewardAmount = Number(input.rewardAmount); - const data = await request("/api/demo/run", { - method: "POST", - body: JSON.stringify(input) - }); - liveStatus.textContent = "Settlement receipt verified"; - renderTimeline(data.proof.steps); - renderReceipt(data.proof); - } catch (error) { - liveStatus.textContent = "Ledger run failed"; - timeline.innerHTML = `
${escapeHtml(error.message)}
`; - } finally { - liveRun.disabled = !health.lifecycleConfigured; - } +$('#refresh-health').addEventListener('click',health); +const names={BOUNTY_ON_LEDGER:'Bounty created',CLAIM_REQUESTED:'Claim requested',CLAIMED:'Claim accepted',PR_SUBMITTED:'Pull request submitted',VERIFIED:'Merge verified',SETTLED:'Settlement recorded'}; +function render(proof) { + $('#timeline').innerHTML=proof.steps.map((step,i)=>`
${i+1}. ${escape(names[step.name]||step.name)}Ledger offset ${escape(step.completionOffset)}
Inspect full ledger referencesContract ID${escape(step.contractId)}Update ID${escape(step.updateId)}Template${escape(step.templateId)}
`).join(''); + const r=proof.settlementReceipt; + $('#receipt-card').classList.remove('hidden'); + $('#receipt-card').innerHTML=`

SETTLEMENT RECEIPT / DEMO VALUE

${escape(r.rewardAmount)} ${escape(r.rewardUnit)}

Repository and issue${escape(r.repository)} #${escape(r.issueNumber)}
Pull request#${escape(r.pullRequest.prNumber)}
Merge commit SHA${escape(r.mergeCommitSha)}
Evidence hash${escape(r.evidenceHash)}
${proof.negativeChecks.map(c=>`
${escape(c.name)}${escape(c.code)}
Inspect rejection response
${escape(JSON.stringify(c.response,null,2))}
`).join('')}

${escape(proof.environment)}. This receipt does not represent a transfer of real money.

`; + $('#download-proof').addEventListener('click',()=>{ + const url=URL.createObjectURL(new Blob([JSON.stringify(lastProof,null,2)+'\n'],{type:'application/json'})); + const a=document.createElement('a');a.href=url;a.download=`commitledger-proof-${lastProof.runId}.json`;a.click();setTimeout(()=>URL.revokeObjectURL(url),1000); + }); +} +$('#live-form').addEventListener('submit',async event=>{ + event.preventDefault();if(!ready)return; + lastProof=null;$('#live-run').disabled=true;$('#receipt-card').classList.add('hidden'); + $('#live-status').textContent='Executing real ledger commands';$('#timeline').innerHTML='
Waiting for actual Canton responses. No simulated success.
'; + try {const data=await request('/api/demo/run',Object.fromEntries(new FormData(event.target)));lastProof=data.proof;render(lastProof);$('#live-status').textContent='Settlement receipt returned';} + catch(error){$('#live-status').textContent='Ledger run failed';$('#timeline').innerHTML=`
${escape(error.message)}

No successful proof bundle was produced.

`;} + finally{$('#live-run').disabled=!ready;} }); - -bountyForm.addEventListener("submit", async (event) => { - event.preventDefault(); - issueStatus.textContent = "Verifying"; - bountySummary.className = "summary muted"; - bountySummary.textContent = "Reading canonical GitHub issue…"; - try { - const input = Object.fromEntries(new FormData(bountyForm)); - input.rewardAmount = Number(input.rewardAmount); - const data = await request("/api/bounty/prepare", { - method: "POST", - body: JSON.stringify(input) - }); - issueStatus.textContent = "Issue verified"; - bountySummary.className = "summary"; - bountySummary.innerHTML = ` - ${escapeHtml(data.bounty.title)} - ${escapeHtml(data.bounty.repository)} #${data.bounty.issueNumber} - ${data.bounty.rewardAmount} ${escapeHtml(data.bounty.rewardUnit)} · demo/test value - `; - } catch (error) { - issueStatus.textContent = "Rejected"; - bountySummary.textContent = error.message; - } +$('#bounty-form').addEventListener('submit',async event=>{ + event.preventDefault();$('#issue-status').textContent='Verifying'; + try {const {bounty}=await request('/api/bounty/prepare',Object.fromEntries(new FormData(event.target)));$('#issue-status').textContent='Issue verified';$('#bounty-summary').textContent=`${bounty.title} / ${bounty.repository} #${bounty.issueNumber} / ${bounty.rewardAmount} ${bounty.rewardUnit}`;} + catch(error){$('#issue-status').textContent='Verification failed';$('#bounty-summary').textContent=error.message;} }); - -verifyForm.addEventListener("submit", async (event) => { - event.preventDefault(); - githubStatus.textContent = "Verifying"; - githubOutput.textContent = "Reading canonical GitHub pull request…"; - const values = Object.fromEntries(new FormData(verifyForm)); - values.issueNumber = Number(values.issueNumber); - values.prNumber = Number(values.prNumber); - try { - const data = await request("/api/github/verify", { - method: "POST", - body: JSON.stringify(values) - }); - githubStatus.textContent = "Merge verified"; - githubOutput.textContent = JSON.stringify(data.evidence, null, 2); - } catch (error) { - githubStatus.textContent = "Rejected"; - githubOutput.textContent = error.message; - } +$('#verify-form').addEventListener('submit',async event=>{ + event.preventDefault();$('#github-status').textContent='Verifying'; + try {const {evidence}=await request('/api/github/verify',Object.fromEntries(new FormData(event.target)));$('#github-status').textContent='Merge verified';$('#github-output').textContent=JSON.stringify(evidence,null,2);} + catch(error){$('#github-status').textContent='Verification failed';$('#github-output').textContent=error.message;} }); - -function renderTimeline(steps) { - timeline.innerHTML = steps.map((step, index) => ` -
-
${String(index + 1).padStart(2, "0")}
-
- ${escapeHtml(step.name)} - contract · ${escapeHtml(shortId(step.contractId))} - update · ${escapeHtml(shortId(step.updateId))} · offset ${step.completionOffset} -
-
- `).join(""); -} - -function renderReceipt(proof) { - const receipt = proof.settlementReceipt || {}; - receiptCard.classList.remove("hidden"); - receiptCard.innerHTML = ` -

SETTLEMENT RECEIPT

-

${escapeHtml(receipt.bountyId || "Settled bounty")}

-
-
Repository${escapeHtml(receipt.repository || "")}
-
Pull request#${receipt.pullRequest?.prNumber ?? ""}
-
Reward${escapeHtml(receipt.rewardAmount || "")} ${escapeHtml(receipt.rewardUnit || "")}
-
Evidence${escapeHtml(shortId(receipt.evidenceHash || ""))}
-
Settlement ref${escapeHtml(receipt.settlementRef || "")}
-
Canton proof${proof.steps.length} committed transitions
-
Negative checks${proof.negativeChecks?.filter(check => check.rejected).length || 0} enforced rejections
-
- `; -} - -function shortId(value) { - const text = String(value || ""); - return text.length > 22 ? `${text.slice(0, 10)}…${text.slice(-8)}` : text; -} - -function escapeHtml(value) { - return String(value).replace(/[&<>"']/g, char => ({ - "&": "&", - "<": "<", - ">": ">", - '"': """, - "'": "'" - })[char]); -} - -checkHealth(); +health(); diff --git a/web/index.html b/web/index.html index 33415c1..e63dc0f 100644 --- a/web/index.html +++ b/web/index.html @@ -1,136 +1,22 @@ - - - - CommitLedger — Verified work, settled on Canton - + + +CommitLedger - Verified work, settled on Canton - - -
-
-

GITHUB EVIDENCE × DAML AUTHORIZATION × CANTON SETTLEMENT

-

Work is not settled until the proof is verifiable.

-

CommitLedger turns a real GitHub issue into a role-authorized bounty lifecycle and records the final evidence-bound receipt on Canton.

-
- 1 · Issue→2 · Bounty→3 · Claim→4 · PR→5 · Verify→6 · Settle -
-
- -
-
-
-

LIVE CANTON PROOF

-

Run the complete six-step lifecycle

-
- Checking runtime -
-

This button performs real JSON Ledger API submissions. It is disabled until Canton package, three distinct parties, and authorized token(s) are configured.

-
- -
- - - -
- -
-
-
No Canton lifecycle has been executed in this session.
-
- -
- -
-
-
-
-

CANONICAL ISSUE

-

Verify a real GitHub work source

-
- Waiting -
-
- -
- - -
- -
-
GitHub is queried server-side. The browser cannot invent the repository, issue title, or issue state.
-
- - -
- -
-
-
-
-

CANONICAL MERGE

-

Verify the exact merged pull request

-
- Waiting -
-
- -
- - - -
- - -
-
No merge evidence loaded.
-
- - -
- -
-

JUDGE PROOF

-

Every important claim leaves an inspectable artifact.

-
-
01Real issue

Canonical GitHub provider state.

-
02Contract IDs

One active contract at every stage.

-
03Update IDs

Every Canton submission is traceable.

-
04Evidence hash

Exact merged PR bound to settlement.

-
05Receipt

Final auditable Canton contract.

-
-
-
- - - - - + +
+

GITHUB EVIDENCE / DAML AUTHORIZATION / CANTON SETTLEMENT

Work is not settled until the proof is verifiable.

Bind a real GitHub issue and merged pull request to an authorization-controlled settlement receipt on Canton.

Issue→Bounty→Claim→Pull request→Verify→Settle
+
+

LIVE CANTON PROOF

Run the complete six-step lifecycle

Checking runtime
+

The ledger must be configured and reachable. No sample transaction IDs or simulated settlement success are displayed. DEMO_CREDIT is not real money.

+
+

+
No Canton lifecycle has been executed in this session.
+
+

CANONICAL ISSUE

Verify the work source

Waiting
Issue identity and state are fetched server-side from GitHub.
+

CANONICAL MERGE

Verify the merged pull request

Waiting
No merge evidence loaded.
+
+ diff --git a/web/styles.css b/web/styles.css index 47c8935..0f05b83 100644 --- a/web/styles.css +++ b/web/styles.css @@ -1 +1 @@ -:root{font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#eef4f5;background:#061014;line-height:1.5}*{box-sizing:border-box}body{margin:0;background:radial-gradient(circle at 18% -8%,#153b43 0,transparent 32%),linear-gradient(#071216,#061014);color:#eef4f5}.nav{height:76px;display:flex;align-items:center;justify-content:space-between;max-width:1200px;margin:auto;padding:0 24px;border-bottom:1px solid #1d3339}.brand{font-size:21px;font-weight:800;color:#fff;text-decoration:none;letter-spacing:-.035em}.subbrand{font-size:12px;color:#6f8d94;margin-left:12px}.nav-right{display:flex;gap:8px}.badge,.status{display:inline-flex;align-items:center;font-size:11px;border:1px solid #315058;border-radius:999px;padding:6px 10px;color:#b9ced3;background:#0b181d;white-space:nowrap}main{max-width:1200px;margin:auto;padding:52px 24px 84px}.hero{max-width:1050px;padding:42px 0 48px}.eyebrow,.kicker{font-size:11px;letter-spacing:.17em;color:#82adb6;font-weight:800;margin:0 0 12px}.hero h1{font-size:clamp(44px,7.2vw,86px);line-height:.96;letter-spacing:-.06em;margin:0;max-width:1080px}.lead{font-size:20px;color:#9eb5ba;max-width:820px;margin:24px 0}.flow{display:flex;flex-wrap:wrap;gap:9px;align-items:center;margin-top:28px}.flow span{padding:8px 11px;background:#0b171c;border:1px solid #254149;border-radius:9px;color:#a9bec3;font-size:13px}.flow b{color:#55767e}.workspace{display:grid;grid-template-columns:minmax(0,1.35fr) minmax(280px,.65fr);gap:18px;margin-bottom:18px}.panel{border:1px solid #203940;background:rgba(8,20,25,.88);border-radius:18px;padding:24px;box-shadow:0 22px 70px rgba(0,0,0,.2)}.live-panel{margin-bottom:18px;border-color:#365f68;background:linear-gradient(135deg,rgba(11,31,36,.96),rgba(7,20,24,.9))}.primary{min-height:100%}.proof-card{background:rgba(8,17,21,.7)}.panel-head{display:flex;justify-content:space-between;gap:18px;align-items:flex-start}.panel h2,.proof h2{font-size:26px;letter-spacing:-.04em;margin:0 0 18px}.metric{border-top:1px solid #213840;padding:14px 0;display:grid;gap:2px}.metric strong{font-size:14px}.metric span,.muted{color:#829ca2;font-size:14px}label{display:grid;gap:7px;font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:#8da7ad;margin:12px 0}input{width:100%;border:1px solid #29474f;border-radius:10px;background:#061014;color:#eef5f6;padding:12px 13px;font:inherit;text-transform:none;letter-spacing:0;outline:none}input:focus{border-color:#78aab5;box-shadow:0 0 0 3px rgba(120,170,181,.12)}input:disabled{opacity:.65}.row{display:grid;grid-template-columns:1fr 1fr;gap:12px}.row.three{grid-template-columns:.7fr 1.2fr .7fr}button{border:0;border-radius:10px;background:#e6f0f1;color:#061014;padding:12px 16px;font-weight:850;cursor:pointer;margin-top:6px}button:hover:not(:disabled){transform:translateY(-1px)}button:disabled{cursor:not-allowed;opacity:.45}.summary{display:grid;gap:4px;margin-top:18px;padding:15px;border:1px solid #2c4b53;border-radius:12px;background:#08171b}.summary strong{font-size:16px}.summary span{color:#91aab0;font-size:13px}.output{min-height:150px;max-height:320px;overflow:auto;background:#040b0d;border:1px solid #1a3036;border-radius:10px;padding:14px;color:#9fc0c6;font-size:12px;white-space:pre-wrap}.checks{padding-left:18px;color:#9fb5ba}.checks li{margin:14px 0}.timeline{display:grid;grid-template-columns:repeat(5,1fr);gap:10px;margin-top:22px}.timeline-empty,.timeline-error{grid-column:1/-1;padding:18px;border:1px dashed #315059;border-radius:12px;color:#829ba1}.timeline-error{border-color:#704047;color:#d5a7ac}.timeline-step{display:flex;gap:10px;border:1px solid #29474e;border-radius:12px;padding:14px;background:#071519;min-width:0}.step-index{font-size:12px;color:#6e9da7;font-weight:800}.timeline-step strong,.timeline-step span{display:block}.timeline-step strong{font-size:13px;margin-bottom:5px}.timeline-step span{font-size:11px;color:#7f9ca3;overflow:hidden;text-overflow:ellipsis}.receipt{margin-top:16px;padding:20px;border:1px solid #47717a;border-radius:14px;background:rgba(15,38,44,.72)}.receipt.hidden{display:none}.receipt h3{margin:0 0 15px;font-size:24px;letter-spacing:-.03em}.receipt-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}.receipt-grid div{border-top:1px solid #35565e;padding-top:10px;min-width:0}.receipt-grid span,.receipt-grid strong{display:block}.receipt-grid span{font-size:10px;color:#6f9098;text-transform:uppercase;letter-spacing:.08em}.receipt-grid strong{font-size:12px;margin-top:4px;overflow:hidden;text-overflow:ellipsis}.proof{padding-top:68px}.proof-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:12px}.proof-grid>div{border-top:1px solid #315159;padding:17px 4px}.proof-grid strong{display:block;font-size:25px;color:#74a7b1}.proof-grid span{display:block;color:#f0f5f6;font-weight:750;margin:8px 0}.proof-grid p{font-size:13px;color:#7d979d;margin:0}footer{max-width:1200px;margin:auto;padding:24px;border-top:1px solid #1d3339;color:#68858c;font-size:12px}@media(max-width:900px){.workspace,.proof-grid,.timeline,.receipt-grid{grid-template-columns:1fr}.row,.row.three{grid-template-columns:1fr}.hero{padding-top:18px}.panel-head{display:block}.status{margin-bottom:10px}.subbrand{display:none}.nav-right .badge:first-child{display:none}} \ No newline at end of file +:root{font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#eef4f5;background:#061014;line-height:1.5}*{box-sizing:border-box}body{margin:0;background:radial-gradient(circle at 18% -8%,#153b43 0,transparent 32%),linear-gradient(#071216,#061014)}.nav{min-height:76px;display:flex;align-items:center;justify-content:space-between;max-width:1200px;margin:auto;padding:16px 24px;border-bottom:1px solid #1d3339;gap:12px}.brand{font-size:21px;font-weight:800;color:#fff;text-decoration:none;letter-spacing:-.035em}.nav-right{display:flex;gap:8px}.badge,.status{display:inline-flex;align-items:center;font-size:11px;border:1px solid #315058;border-radius:999px;padding:6px 10px;color:#b9ced3;background:#0b181d}main{max-width:1200px;margin:auto;padding:32px 24px 64px}.hero{max-width:1050px;padding:42px 0 48px}.eyebrow,.kicker{font-size:11px;letter-spacing:.15em;color:#91bac3;font-weight:800;margin:0 0 12px}.hero h1{font-size:clamp(40px,6.5vw,78px);line-height:1.03;letter-spacing:-.055em;margin:0}.lead{font-size:20px;color:#a9c0c6;max-width:820px;margin:24px 0}.flow{display:flex;flex-wrap:wrap;gap:9px;align-items:center;margin-top:28px}.flow span{padding:8px 11px;background:#0b171c;border:1px solid #254149;border-radius:9px;color:#a9bec3;font-size:13px}.flow b{color:#55767e}.workspace{display:grid;grid-template-columns:minmax(0,1.35fr) minmax(280px,.65fr);gap:18px;margin-bottom:18px}.panel{border:1px solid #203940;background:rgba(8,20,25,.88);border-radius:18px;padding:24px;min-width:0}.live-panel{margin-bottom:18px;border-color:#365f68;background:linear-gradient(135deg,rgba(11,31,36,.96),rgba(7,20,24,.9))}.panel-head{display:flex;justify-content:space-between;gap:18px;align-items:flex-start}.panel h2{font-size:26px;letter-spacing:-.03em;margin:0 0 18px}.metric{border-top:1px solid #213840;padding:14px 0;display:grid;gap:3px}.metric strong{font-size:14px}.metric span,.muted{color:#a0b6bc;font-size:14px}label{display:grid;gap:7px;font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:#a7bfc5;margin:12px 0;min-width:0}input{width:100%;min-width:0;border:1px solid #29474f;border-radius:10px;background:#061014;color:#eef5f6;padding:12px 13px;font:inherit;text-transform:none;letter-spacing:0}input:focus-visible,button:focus-visible{outline:2px solid #a3d6df;outline-offset:3px}input:disabled{opacity:.75}.row{display:grid;grid-template-columns:1fr 1fr;gap:12px}.row.three{grid-template-columns:.7fr 1.2fr .7fr}button{border:0;border-radius:10px;background:#e6f0f1;color:#061014;padding:12px 16px;font-weight:800;cursor:pointer;margin:6px 6px 0 0}button:disabled{cursor:not-allowed;opacity:.45}.secondary{background:#193740;color:#d8e9ed;border:1px solid #365963}.summary{display:grid;gap:4px;margin-top:18px;padding:15px;border:1px solid #2c4b53;border-radius:12px;background:#08171b}.output{min-height:150px;max-height:360px;overflow:auto;background:#040b0d;border:1px solid #1a3036;border-radius:10px;padding:14px;color:#b2d1d7;font-size:12px;white-space:pre-wrap;overflow-wrap:anywhere}.timeline{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin-top:22px}.timeline-empty,.timeline-error{grid-column:1/-1;padding:18px;border:1px dashed #315059;border-radius:12px;color:#a1b9bf}.timeline-error{border-color:#704047;color:#efbec2}.timeline-step{border:1px solid #29474e;border-radius:12px;padding:14px;background:#071519;min-width:0}.timeline-step strong,.timeline-step span{display:block}.timeline-step strong{font-size:14px;margin-bottom:5px}.timeline-step span{font-size:12px;color:#a1bac1}.timeline-step code,.receipt code{display:block;overflow-wrap:anywhere;font-size:11px;margin:5px 0;color:#c1dce2}details{margin-top:8px}summary{cursor:pointer;font-size:12px}.receipt{margin-top:16px;padding:20px;border:1px solid #47717a;border-radius:14px;background:rgba(15,38,44,.72);overflow-wrap:anywhere}.hidden{display:none}.receipt h3{margin:0 0 15px;font-size:22px}.receipt-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}.receipt-grid span,.receipt-grid strong{display:block}.receipt-grid span{font-size:11px;color:#a2bdc5}.receipt-grid strong{font-size:13px;margin-top:4px}.negative{border-top:1px solid #35565e;margin-top:12px;padding-top:12px}footer{max-width:1200px;margin:auto;padding:24px;border-top:1px solid #1d3339;color:#98b0b7;font-size:12px}@media(max-width:900px){.workspace,.timeline,.receipt-grid,.row,.row.three{grid-template-columns:1fr}.hero{padding-top:18px}.panel-head{display:block}.status{margin-bottom:10px}.panel{padding:20px}main{padding:20px 16px 40px}.nav{padding:14px 16px}.nav-right .badge:last-child{display:none}.eyebrow{line-height:1.8}.lead{font-size:17px}} From c0deb146d802317aec2f2c92c56eb7b113cab50d Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 19:28:24 +0600 Subject: [PATCH 002/144] docs: lock HackCanton S3 competition-ready design --- ...-hackcanton-s3-competition-ready-design.md | 244 ++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-29-hackcanton-s3-competition-ready-design.md diff --git a/docs/superpowers/specs/2026-09-29-hackcanton-s3-competition-ready-design.md b/docs/superpowers/specs/2026-09-29-hackcanton-s3-competition-ready-design.md new file mode 100644 index 0000000..fa49579 --- /dev/null +++ b/docs/superpowers/specs/2026-09-29-hackcanton-s3-competition-ready-design.md @@ -0,0 +1,244 @@ +# HackCanton Season 3 Competition-Ready Design + +**Status:** Design specification for CommitLedger delivery branch `fix/verified-delivery-20260929`. +**Rules snapshot:** HackCanton Season 3 live Rules/Timeline/Tracks/Materials checked 2026-09-29. +**Target track:** Track 1 — Real-World Assets (RWA) & Business Workflows. + +## 1. Goal + +Ship CommitLedger as a judge-runnable Canton application whose core value is provable end-to-end settlement of verified open-source work: + +`GitHub Issue -> Daml Bounty -> Claim -> Pull Request -> GitHub Merge Verification -> Canton Settlement -> Auditable Receipt`. + +The submission must satisfy the official Season 3 requirements and make the six judging criteria easy to verify: Value, ICP, Metrics/Validation, GTM, MVP, and Pitch. + +## 2. Competition requirements that are binding + +The final submission must: +- be built on Canton and make meaningful use of Daml, Canton nodes, or Canton APIs; +- submit to exactly one official track: Track 1; +- contain hackathon-period work from 2026-09-18 through 2026-10-09 that is clearly identifiable, with any pre-existing code disclosed; +- use a public repository with a README at final submission time; +- make every judging artifact publicly accessible without requesting access; +- provide a working prototype/live demo or recorded demo video of at most 5 minutes; +- provide concise pitch material covering problem, Canton use, target users/GTM, and validation/metrics; +- have a completed project profile, one selected track, at least one journal entry, and the submitting account's required 1,000 Mana / minimum 10 days platform activity; +- submit no later than 2026-10-09 23:59 UTC; +- avoid plagiarism, misrepresentation, artificial activity/Mana inflation, IP violations, and undisclosed AI assistance. + +Account-level gates (Mana, activity days, project profile, journal) are real submission blockers but are outside repository code. They must be verified before final submission. + +## 3. Product positioning for Track 1 + +CommitLedger is a business workflow for auditable contributor settlement. It maps cleanly to Track 1: +- **Create:** maintainer creates a bounty tied to a canonical GitHub issue. +- **Update status:** contributor claims and submits a pull request. +- **Fulfill:** verifier independently checks the merged pull request and evidence. +- **Audit/report:** Canton records the authorized settlement and final receipt. + +The demo asset remains `DEMO_CREDIT`. The product must not claim fiat transfer, Canton Coin transfer, MainNet usage, real users, production custody, or traction without evidence. + +## 4. Roles and trust boundaries + +### Maintainer +Creates the bounty, accepts the contributor claim, and performs settlement after verification. + +### Contributor +Claims the bounty and submits the GitHub pull request reference. + +### Verifier +Checks canonical GitHub evidence and advances only valid merged work. + +### Canton/Daml +Enforces role authorization, state transitions, and exactly-once settlement semantics. + +### GitHub +Provides the external source of truth for repository, issue, pull request, merge status, merge commit SHA, base branch, author policy, and merge timestamp. + +The browser is never trusted to self-report GitHub merge state or ledger success. + +## 5. Security and correctness requirements + +The MVP must fail closed. + +Required positive proof: +1. canonical issue is fetched; +2. bounty is created on Canton; +3. contributor claim is accepted; +4. real pull request is bound to that bounty; +5. canonical merged PR evidence is independently fetched; +6. merge commit is validated and reachable from the expected base branch; +7. verifier authorization succeeds; +8. maintainer settlement succeeds; +9. active SettlementReceipt is found and exported; +10. full contract/update identifiers are preserved. + +Required negative proof: +- cross-repository issue lookalikes are rejected; +- wrong issue evidence is rejected; +- unmerged or malformed merge evidence is rejected; +- unauthorized settlement is rejected; +- replay/duplicate settlement is rejected; +- network, authentication, timeout, rate-limit, and unknown infrastructure errors never count as Daml authorization/replay evidence. + +## 6. Runtime proof + +The final technical proof must run on a real Canton environment, not a mocked transport. + +Required evidence from one coherent run: +- Node test log; +- Daml build success; +- Daml Script test success; +- DAR/package deployment; +- distinct maintainer, contributor, verifier parties; +- six lifecycle ledger transitions; +- update IDs and contract IDs; +- final SettlementReceipt; +- exact negative-rejection responses; +- evidence JSON tied to the source commit; +- logs sufficient to distinguish ledger enforcement from infrastructure failure. + +Local sandbox proof is acceptable as engineering evidence unless the organizers explicitly require a different network. If HackCanton DevNet access is available, the final judge package should prefer it because it reduces judge setup friction. + +## 7. Judge-facing product flow + +The UI should make the value understandable in 60–90 seconds. + +### Screen hierarchy +1. **Problem/value statement** — merged work should not settle without independently verifiable evidence. +2. **Live Canton lifecycle** — one primary action runs the full proof. +3. **Role separation** — Maintainer / Contributor / Verifier responsibilities. +4. **Canonical GitHub evidence** — issue, PR, author, base branch, head SHA, merge commit SHA, merged timestamp. +5. **Ledger timeline** — human-readable state name plus full update ID and contract ID. +6. **Security failures** — wrong evidence, unauthorized settlement, and replay are visibly rejected. +7. **Settlement receipt** — final receipt, evidence hash, merge commit, settlement reference, and export. +8. **Why Canton** — multi-party authorization, privacy-compatible workflow semantics, and auditable state transitions. + +No fake sample transaction IDs or simulated success states may appear as if they were runtime evidence. + +## 8. Judging-criteria evidence + +### Value / Problem Statement +Explain the coordination problem: GitHub proves code events, but it does not by itself enforce who may verify or settle a bounty. + +### ICP / Audience +Primary ICP: open-source maintainers, foundations, ecosystem grant programs, and organizations running contributor bounty workflows. + +### Metrics / Validation +Use only evidence we possess: +- real GitHub issue/PR fixture; +- deterministic positive and negative tests; +- runtime Canton proof when captured; +- judge walkthrough completion. +Do not fabricate user, wallet, transaction, or revenue metrics. + +### GTM +Pilot path: +1. one repository/maintainer workflow; +2. one ecosystem or foundation bounty program; +3. integrate additional repositories and settlement adapters after validation. + +### MVP +Show real code, Daml lifecycle, GitHub verification, Canton writes/reads, role authorization, replay prevention, and evidence export. + +### Pitch +Keep the submission/demo narrative concise: +`problem -> why GitHub alone is insufficient -> live workflow -> rejected attacks -> Canton receipt -> business use -> pilot path`. + +## 9. Track 1 submission package + +Create: +- role-based working MVP; +- one-page business brief with ICP, use case, who pays, and why Canton; +- short 2–3 step pilot plan and required integrations; +- concise pitch deck/document; +- ≤5-minute recorded demo; +- public README/repository at final release; +- publicly accessible demo/project page and evidence package. + +Repository visibility changes and public publishing are intentionally deferred until the owner requests the final submission/publishing phase. + +## 10. Automation + +The delivery workflow should automate everything that can be truthfully automated: +- Node tests; +- Daml build/tests; +- Canton local proof where runner support exists; +- evidence validation; +- repository safety/compliance gates; +- artifact retention; +- browser/judge-flow smoke checks when a browser is available. + +Automation must never convert a missing real runtime into a green "submission ready" state. + +A single competition readiness check should report three categories: +- **PASS:** verified with evidence; +- **BLOCKED:** requires account, network, browser, visibility, or external state; +- **FAIL:** implementation/evidence is incorrect. + +## 11. Figma/design system + +The judge UI should be refined in Figma before final implementation polish: +- high-contrast dark interface aligned with existing code; +- one primary action per proof stage; +- clear success/rejection semantics that do not rely on color alone; +- readable full identifiers with copy affordances; +- desktop judge layout plus mobile fallback; +- no marketing-heavy decorative screens that hide evidence; +- consistent components/tokens that map directly to the web implementation. + +## 12. Delivery tracking + +Linear should track only evidence-backed work. Each competition requirement gets an issue with: +- official rule/criterion; +- implementation task; +- verification command; +- evidence artifact; +- status: Todo / In Progress / Blocked / Verified. + +Do not mark a task verified from source inspection alone when the requirement is runtime or account dependent. + +## 13. Scope exclusions until the core is verified + +Do not add: +- token/DAO/reputation systems; +- general freelance marketplace features; +- paid hosting dependencies; +- production custody; +- MainNet claims; +- fake traction; +- optional sponsor integrations that distract from the Track 1 core flow. + +Optional integrations are considered only after core compliance, runtime proof, judge UX, and submission materials are green. + +## 14. Definition of done + +CommitLedger is product-ready when: +- full Node suite is green; +- Daml build/test is green; +- a real Canton lifecycle reaches SettlementReceipt; +- the named negative cases produce specific ledger/Daml rejection evidence; +- browser judge flow passes desktop/mobile QA; +- Track 1 business brief and pilot plan are complete; +- pitch/demo material is complete and truthful; +- hackathon-period work and pre-existing work are disclosed; +- no secrets are committed; +- competition readiness checker has no implementation FAILs. + +CommitLedger is **submission-ready** only after the additional external gates pass: +- repository/public artifacts are accessible to a logged-out judge; +- 1,000 Mana requirement is satisfied; +- minimum 10 days platform activity is satisfied; +- journal is non-empty; +- project profile is complete; +- final track is Track 1; +- all links work in a private browser window; +- final submission is delivered before 2026-10-09 23:59 UTC. + +## 15. Source of truth + +Official Season 3 sources used for this spec: +- https://hackathon.appsfactory.cc/season-3 +- https://www.youtube.com/watch?v=_PesNP65N7I + +If organizers change the live rules, the newer official rule controls. Re-run the compliance check before final submission. From cff45597fa9db6b5e1c48d82996e4fba29b64659 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 19:31:37 +0600 Subject: [PATCH 003/144] docs: add competition-ready delivery plan --- .../2026-09-29-competition-ready-delivery.md | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 docs/superpowers/plans/2026-09-29-competition-ready-delivery.md diff --git a/docs/superpowers/plans/2026-09-29-competition-ready-delivery.md b/docs/superpowers/plans/2026-09-29-competition-ready-delivery.md new file mode 100644 index 0000000..c4ad567 --- /dev/null +++ b/docs/superpowers/plans/2026-09-29-competition-ready-delivery.md @@ -0,0 +1,161 @@ +# CommitLedger Competition-Ready Delivery Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Deliver a HackCanton Season 3 Track 1-ready CommitLedger build with evidence-backed compliance automation, real Canton proof, judge-facing UX, and complete business/pitch materials while deferring repository visibility and final portal submission. + +**Architecture:** Keep the existing GitHub verifier -> Daml lifecycle -> Canton JSON Ledger API architecture. Add a fail-closed competition readiness model, evidence manifests, Track 1 business artifacts, runtime proof automation, and a judge-facing proof dashboard. External account/publication gates remain BLOCKED rather than being faked green. + +**Tech Stack:** Node.js 22, Daml/DPM 3.5.12, Canton JSON Ledger API v2, vanilla web UI, GitHub Actions, Figma, Linear. + +**Spec:** `docs/superpowers/specs/2026-09-29-hackcanton-s3-competition-ready-design.md` + +## Global Constraints + +- Target exactly one competition track: Track 1 — Real-World Assets (RWA) & Business Workflows. +- Submission deadline: 2026-10-09 23:59 UTC. +- Meaningful Canton integration must be demonstrated by a real runtime, not mocked transport. +- Final video must be no longer than 5 minutes. +- Public repository/public links, Mana/activity, journal, and project-profile gates are deferred external blockers; automation must report them as BLOCKED until verified. +- Never claim MainNet, Canton Coin/fiat settlement, production custody, users, traction, or metrics without evidence. +- DEMO_CREDIT is non-production test value. +- AI assistance must be disclosed in submission materials. +- Work before 2026-09-18 must be disclosed as pre-existing; judges evaluate hackathon-period work. + +## Review Focus + +- A missing external/account gate must never become PASS. +- A network/auth/rate-limit error must never count as a Daml security rejection. +- Evidence from different source commits/runs must never be combined into one green readiness result. +- UI must never render sample identifiers as if they were real runtime proof. +- Track 1 business claims must stay evidence-based and avoid invented validation. + +--- + +### Task 1: Competition readiness engine + +**Files:** +- Create: `src/competition-readiness.mjs` +- Create: `tests/competition-readiness.test.mjs` +- Create: `scripts/check-competition-readiness.mjs` +- Modify: `package.json` + +**Interfaces:** +- Produces: `evaluateCompetitionReadiness(input) -> { status, gates, counts }` +- Produces CLI JSON/Markdown summary from evidence + external-gate input. + +- [ ] Write failing tests for PASS/BLOCKED/FAIL semantics and mixed-run rejection. +- [ ] Run targeted test and verify RED. +- [ ] Implement the minimal readiness evaluator. +- [ ] Add CLI wrapper and package script. +- [ ] Run targeted test and full `npm test`. +- [ ] Commit. + +### Task 2: Track 1 business and submission package + +**Files:** +- Create: `docs/TRACK1_BUSINESS_BRIEF.md` +- Create: `docs/PILOT_PLAN.md` +- Create: `docs/AI_DISCLOSURE.md` +- Create: `docs/HACKATHON_WORK_DISCLOSURE.md` +- Modify: `docs/SUBMISSION_DRAFT.md` +- Modify: `docs/FINAL_CHECKLIST.md` + +**Interfaces:** +- Consumes official rules/spec. +- Produces judge-ready factual copy with no fabricated traction. + +- [ ] Draft Track 1 one-page business brief. +- [ ] Draft 2–3 step pilot plan. +- [ ] Add explicit AI and pre-existing-work disclosures. +- [ ] Align submission draft/checklist to official S3 gates. +- [ ] Repository content review for unsupported claims. +- [ ] Commit. + +### Task 3: Real Canton proof on connected development machine + +**Files:** +- Modify as required: `scripts/bootstrap-dpm.sh`, `scripts/run-local-proof.sh`, `scripts/verify-all.sh`, runtime code/tests. +- Evidence output: `evidence/*` (ignored except placeholders). + +**Interfaces:** +- Produces one coherent evidence bundle from a single source commit/run. +- Consumed by Task 1 readiness engine and judge UI. + +- [ ] Verify machine prerequisites and install pinned DPM 3.5.12. +- [ ] Run full Node suite. +- [ ] Run Daml build/test. +- [ ] Start fresh Canton sandbox and deploy DAR. +- [ ] Execute GitHub -> Canton lifecycle through SettlementReceipt. +- [ ] Capture wrong-evidence, unauthorized-settlement, duplicate-settlement rejections. +- [ ] Validate evidence bundle and source-commit consistency. +- [ ] If a code/script defect appears, reproduce with a failing test before fixing. +- [ ] Commit any fixes; never commit generated secrets/evidence. + +### Task 4: Judge proof dashboard and compliance view + +**Files:** +- Modify: `web/index.html` +- Modify: `web/app.js` +- Modify: `web/styles.css` +- Add/modify UI tests as supported by existing test approach. + +**Interfaces:** +- Consumes runtime proof and readiness summary. +- Produces 60–90 second judge flow and explicit six-criteria evidence view. + +- [ ] Add failing tests for readiness rendering/no fake PASS where feasible. +- [ ] Implement Value / ICP / Validation / GTM / MVP / Pitch evidence section. +- [ ] Add Track 1 badge/business workflow mapping. +- [ ] Add readiness PASS/BLOCKED/FAIL panel. +- [ ] Preserve full IDs/copy/export/rejection details. +- [ ] Run tests and browser smoke/mobile QA on connected machine. +- [ ] Commit. + +### Task 5: Figma judge-flow design + +**Files:** Figma design file; no repo code until design is approved against implemented constraints. + +**Interfaces:** +- Consumes implemented judge-flow information architecture. +- Produces editable desktop/mobile judge UI reference. + +- [ ] Create Figma file. +- [ ] Build tokens/components matching existing web implementation. +- [ ] Design hero, lifecycle, proof timeline, security rejection, readiness, business evidence, receipt views. +- [ ] Include desktop and mobile frames. +- [ ] Verify no visual state implies fabricated runtime success. + +### Task 6: Linear competition delivery system + +**Files:** Linear project/issues. + +**Interfaces:** +- Consumes official requirements and evidence gates. +- Produces persistent delivery tracker with Verified vs Blocked distinction. + +- [ ] Create `CommitLedger — HackCanton S3 Delivery` project. +- [ ] Create milestones for Compliance, Runtime Proof, Judge UX, Submission Package. +- [ ] Create issues for each mandatory rule/evidence gate. +- [ ] Link PR #8/spec where supported. +- [ ] Set deadline/target date before 2026-10-09. +- [ ] Keep publication/Mana/profile items BLOCKED until user completes external actions. + +### Task 7: Final evidence review + +**Files:** +- Modify: `docs/DELIVERY_STATUS.md` +- Modify: `docs/EVIDENCE_MANIFEST.md` +- Modify: `README.md` as needed. + +**Interfaces:** +- Consumes all prior task evidence. +- Produces truthful final pre-publication status. + +- [ ] Run full Node suite. +- [ ] Run Daml tests/runtime proof if environment remains available. +- [ ] Run competition readiness checker. +- [ ] Verify browser/mobile judge flow. +- [ ] Review repo for secrets and unsupported claims. +- [ ] Update status docs with exact evidence. +- [ ] Keep PR draft until all implementation gates are green; do not make repo public or submit without explicit final-phase instruction. From 68c2159ec112ee3cfd082f6e3fed0527c4636737 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 19:37:23 +0600 Subject: [PATCH 004/144] feat: add fail-closed competition readiness engine --- src/competition-readiness.mjs | 85 +++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/competition-readiness.mjs diff --git a/src/competition-readiness.mjs b/src/competition-readiness.mjs new file mode 100644 index 0000000..43d4823 --- /dev/null +++ b/src/competition-readiness.mjs @@ -0,0 +1,85 @@ +const IMPLEMENTATION_GATES = [ + "nodeTests", + "damlBuild", + "damlTests", + "cantonLifecycle", + "negativeSecurity", + "judgeUi", + "track1Package", +]; + +const EXTERNAL_GATES = [ + "publicRepository", + "publicArtifacts", + "mana1000", + "activity10Days", + "journalNonEmpty", + "projectProfileComplete", + "track1Selected", +]; + +function normalizeStatus(value, kind) { + const raw = String(value?.status || "").toLowerCase(); + if (raw === "verified" || raw === "pass") return "PASS"; + if (raw === "failed" || raw === "fail") return "FAIL"; + if (raw === "blocked" || raw === "pending" || raw === "unknown" || raw === "") return "BLOCKED"; + return kind === "implementation" ? "FAIL" : "BLOCKED"; +} + +function gateRecord(name, value, kind) { + return { + name, + kind, + status: normalizeStatus(value, kind), + reason: value?.reason || "", + runId: value?.runId || "", + sourceCommit: value?.sourceCommit || "", + }; +} + +function coherenceGate(implementation) { + const verifiedRuntime = IMPLEMENTATION_GATES + .map(name => [name, implementation?.[name]]) + .filter(([, value]) => normalizeStatus(value, "implementation") === "PASS") + .filter(([, value]) => value?.runId || value?.sourceCommit); + + if (verifiedRuntime.length === 0) return null; + + const runIds = new Set(verifiedRuntime.map(([, value]) => value?.runId).filter(Boolean)); + const sourceCommits = new Set(verifiedRuntime.map(([, value]) => value?.sourceCommit).filter(Boolean)); + if (runIds.size <= 1 && sourceCommits.size <= 1) return null; + + return { + name: "evidenceCoherence", + kind: "implementation", + status: "FAIL", + reason: "Verified implementation evidence must come from one coherent run and source commit.", + runId: "", + sourceCommit: "", + }; +} + +export function evaluateCompetitionReadiness({ implementation = {}, external = {} } = {}) { + const gates = [ + ...IMPLEMENTATION_GATES.map(name => gateRecord(name, implementation[name], "implementation")), + ...EXTERNAL_GATES.map(name => gateRecord(name, external[name], "external")), + ]; + + const coherence = coherenceGate(implementation); + if (coherence) gates.push(coherence); + + const counts = gates.reduce((acc, gate) => { + if (gate.status === "PASS") acc.pass += 1; + else if (gate.status === "FAIL") acc.fail += 1; + else acc.blocked += 1; + return acc; + }, { pass: 0, blocked: 0, fail: 0 }); + + const status = counts.fail > 0 ? "FAIL" : counts.blocked > 0 ? "BLOCKED" : "PASS"; + return { status, gates, counts }; +} + +export const competitionGateNames = Object.freeze({ + implementation: [...IMPLEMENTATION_GATES], + external: [...EXTERNAL_GATES], +}); From ffad055f38900b47499483bd948637152cbeed59 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 19:37:28 +0600 Subject: [PATCH 005/144] test: cover competition readiness gates --- tests/competition-readiness.test.mjs | 58 ++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 tests/competition-readiness.test.mjs diff --git a/tests/competition-readiness.test.mjs b/tests/competition-readiness.test.mjs new file mode 100644 index 0000000..ab2c7f7 --- /dev/null +++ b/tests/competition-readiness.test.mjs @@ -0,0 +1,58 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { evaluateCompetitionReadiness } from "../src/competition-readiness.mjs"; + +const verifiedImplementation = { + nodeTests: { status: "verified", runId: "run-1", sourceCommit: "abc123" }, + damlBuild: { status: "verified", runId: "run-1", sourceCommit: "abc123" }, + damlTests: { status: "verified", runId: "run-1", sourceCommit: "abc123" }, + cantonLifecycle: { status: "verified", runId: "run-1", sourceCommit: "abc123" }, + negativeSecurity: { status: "verified", runId: "run-1", sourceCommit: "abc123" }, + judgeUi: { status: "verified", runId: "run-1", sourceCommit: "abc123" }, + track1Package: { status: "verified", runId: "run-1", sourceCommit: "abc123" }, +}; +const verifiedExternal = { + publicRepository: { status: "verified" }, + publicArtifacts: { status: "verified" }, + mana1000: { status: "verified" }, + activity10Days: { status: "verified" }, + journalNonEmpty: { status: "verified" }, + projectProfileComplete: { status: "verified" }, + track1Selected: { status: "verified" }, +}; + +test("returns PASS only when implementation and external gates are verified", () => { + const result = evaluateCompetitionReadiness({ implementation: verifiedImplementation, external: verifiedExternal }); + assert.equal(result.status, "PASS"); + assert.deepEqual(result.counts, { pass: 14, blocked: 0, fail: 0 }); +}); + +test("returns BLOCKED when an external gate is not yet verified", () => { + const result = evaluateCompetitionReadiness({ + implementation: verifiedImplementation, + external: { ...verifiedExternal, mana1000: { status: "blocked", reason: "AppsFactory account not checked" } }, + }); + assert.equal(result.status, "BLOCKED"); + assert.equal(result.gates.find(g => g.name === "mana1000").status, "BLOCKED"); +}); + +test("returns FAIL when an implementation gate fails even if external gates are blocked", () => { + const result = evaluateCompetitionReadiness({ + implementation: { ...verifiedImplementation, damlTests: { status: "failed", reason: "Daml test failed" } }, + external: { ...verifiedExternal, publicRepository: { status: "blocked" } }, + }); + assert.equal(result.status, "FAIL"); + assert.ok(result.counts.fail >= 1); +}); + +test("rejects evidence mixed across runtime run IDs or source commits", () => { + const result = evaluateCompetitionReadiness({ + implementation: { + ...verifiedImplementation, + cantonLifecycle: { status: "verified", runId: "run-2", sourceCommit: "def456" }, + }, + external: verifiedExternal, + }); + assert.equal(result.status, "FAIL"); + assert.equal(result.gates.find(g => g.name === "evidenceCoherence").status, "FAIL"); +}); From 7af636e30c3d3471c4dc69578e3af2901723e85d Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 19:37:32 +0600 Subject: [PATCH 006/144] feat: add competition readiness CLI --- scripts/check-competition-readiness.mjs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 scripts/check-competition-readiness.mjs diff --git a/scripts/check-competition-readiness.mjs b/scripts/check-competition-readiness.mjs new file mode 100644 index 0000000..3784a08 --- /dev/null +++ b/scripts/check-competition-readiness.mjs @@ -0,0 +1,15 @@ +import fs from "node:fs/promises"; +import process from "node:process"; +import { evaluateCompetitionReadiness } from "../src/competition-readiness.mjs"; + +const inputPath = process.argv[2] || process.env.COMMITLEDGER_READINESS_INPUT || "./evidence/readiness-input.json"; + +try { + const input = JSON.parse(await fs.readFile(inputPath, "utf8")); + const result = evaluateCompetitionReadiness(input); + process.stdout.write(`${JSON.stringify(result, null, 2)}\n`); + process.exitCode = result.status === "PASS" ? 0 : result.status === "BLOCKED" ? 2 : 1; +} catch (error) { + process.stderr.write(`Competition readiness check failed: ${error.message}\n`); + process.exitCode = 1; +} From 4be69a8066fbb0a93e5844f571ff3b23f4767714 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 19:37:56 +0600 Subject: [PATCH 007/144] chore: wire competition readiness command --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index d0a62c1..5364be4 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "test": "node --test tests/*.test.mjs", "verify:pr": "node src/verify-pr.mjs", "demo:full": "node src/run-full-demo.mjs", + "readiness": "node scripts/check-competition-readiness.mjs", "check": "npm test" }, "engines": { From 8887ddb59f9146e5ab901b00ab6f2087622f913a Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 19:39:34 +0600 Subject: [PATCH 008/144] docs: add Track 1 business brief --- docs/TRACK1_BUSINESS_BRIEF.md | 46 +++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 docs/TRACK1_BUSINESS_BRIEF.md diff --git a/docs/TRACK1_BUSINESS_BRIEF.md b/docs/TRACK1_BUSINESS_BRIEF.md new file mode 100644 index 0000000..7953943 --- /dev/null +++ b/docs/TRACK1_BUSINESS_BRIEF.md @@ -0,0 +1,46 @@ +# Track 1 Business Brief — CommitLedger + +## Problem +Open-source maintainers and bounty programs can verify code changes on GitHub, but GitHub does not by itself enforce who is allowed to approve a bounty settlement, nor does it provide a multi-party settlement state machine. + +## ICP +Primary users: +- open-source maintainers running issue-based bounties; +- foundations and ecosystem grant programs; +- organizations paying contributors for verified repository work. + +## Use case +A maintainer creates a bounty tied to a canonical GitHub issue. A contributor claims it and submits a pull request. A verifier independently checks the canonical merged PR evidence. Only then can the maintainer settle. Canton records the authorized state transition and final receipt. + +## Who pays +The likely economic buyer is the organization operating the bounty or contributor program: a foundation, protocol, open-source company, ecosystem fund, or enterprise engineering organization. + +No customer revenue or paid pilot is claimed yet. + +## Why Canton +CommitLedger uses Canton/Daml for the part GitHub cannot provide: +- explicit multi-party authorization; +- stateful workflow transitions; +- privacy-compatible role separation; +- consumed-contract semantics that prevent replay of the same verified settlement; +- auditable ledger identifiers and receipt state. + +GitHub remains the source of truth for repository events; Canton is the source of truth for the authorized settlement workflow. + +## Track 1 mapping +Track 1 asks for an end-to-end business workflow with state changes, fulfillment, role-based UI, and audit/reporting. + +CommitLedger maps as: +1. Create — Bounty from a canonical GitHub issue. +2. Update — Claim and PR submission. +3. Fulfill — Independent merge verification. +4. Audit/report — SettlementReceipt with bound evidence. + +## Validation available today +Only evidence that actually exists is used: +- a real GitHub issue/merged-PR fixture in the repository; +- Node tests for canonical verification and failure classification; +- Daml contract tests when executed; +- real Canton runtime evidence only after captured from a successful run. + +No fabricated users, revenue, production transfers, wallet counts, or traction metrics are claimed. From 3f26f0825ab9e00bcc09f83a619ead54989f625f Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 19:39:38 +0600 Subject: [PATCH 009/144] docs: add Track 1 pilot plan --- docs/PILOT_PLAN.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 docs/PILOT_PLAN.md diff --git a/docs/PILOT_PLAN.md b/docs/PILOT_PLAN.md new file mode 100644 index 0000000..12c0a66 --- /dev/null +++ b/docs/PILOT_PLAN.md @@ -0,0 +1,32 @@ +# CommitLedger Pilot Plan + +## Pilot 1 — Single repository +Run CommitLedger with one maintainer-controlled open-source repository and a small set of issue bounties using DEMO_CREDIT/test value. + +Required integrations: +- GitHub API; +- Daml package; +- Canton JSON Ledger API; +- judge/operator web UI. + +Success evidence: +- canonical issue-to-PR binding; +- successful authorized settlement; +- explicit unauthorized and replay rejection; +- reproducible proof export. + +## Pilot 2 — Foundation or ecosystem bounty program +Add a foundation/community operator as the verifier role and run a defined bounty campaign with multiple repositories or maintainers. + +Required additions: +- production-grade identity/token handling; +- operational monitoring; +- organization-level access controls; +- policy for disputes and contributor identity. + +Success measures must be collected from the pilot, not invented in advance: completion rate, time-to-verification, operator effort, failed/flagged claims, and repeat usage. + +## Pilot 3 — Settlement adapter +After workflow validation, connect the verified Canton settlement state to an approved asset/payment rail appropriate for the operator. + +This is future work. The HackCanton MVP does not claim Canton Coin, fiat, MainNet payment, or production custody. From 083b6f4a02b58c9c33e7206e35162c739f81712b Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 19:39:42 +0600 Subject: [PATCH 010/144] docs: disclose AI-assisted development --- docs/AI_DISCLOSURE.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 docs/AI_DISCLOSURE.md diff --git a/docs/AI_DISCLOSURE.md b/docs/AI_DISCLOSURE.md new file mode 100644 index 0000000..50ce481 --- /dev/null +++ b/docs/AI_DISCLOSURE.md @@ -0,0 +1,7 @@ +# AI Assistance Disclosure + +CommitLedger was developed with AI-assisted tooling for research, planning, code review, test generation, documentation, and implementation support. + +All submitted code, contracts, tests, security claims, and competition materials remain the responsibility of the participating team. AI-generated or AI-assisted output is not treated as evidence by itself; runtime and test claims require executed proof. + +No AI tool is represented as a team member, customer, validator, or organizer. From 615a9a23fb04a268fd0b27e036d3ac86385f72d0 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 19:39:46 +0600 Subject: [PATCH 011/144] docs: add hackathon work disclosure --- docs/HACKATHON_WORK_DISCLOSURE.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 docs/HACKATHON_WORK_DISCLOSURE.md diff --git a/docs/HACKATHON_WORK_DISCLOSURE.md b/docs/HACKATHON_WORK_DISCLOSURE.md new file mode 100644 index 0000000..ae0e77a --- /dev/null +++ b/docs/HACKATHON_WORK_DISCLOSURE.md @@ -0,0 +1,11 @@ +# Hackathon Work Disclosure + +HackCanton Season 3 delivery phase: **2026-09-18 through 2026-10-09**. + +CommitLedger may include repository history or scaffolding created before the delivery phase. Pre-existing work is not presented as hackathon-period work. + +The final submission should identify the exact submission commit and use Git history/compare ranges to show the work completed during the official delivery window. Judges should evaluate only the qualifying work under the official rules. + +Major hackathon-period areas include the Daml/Canton workflow, GitHub evidence binding, security hardening, runtime proof automation, judge-facing UI, competition compliance automation, and submission materials, to the extent their commit timestamps fall inside the official delivery period. + +Before final submission, this document must be reconciled against the actual Git history so every material claim is attributable to a qualifying commit range. From f5c217457840d4283666193f321e44edce8443ce Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 19:39:51 +0600 Subject: [PATCH 012/144] docs: align submission draft with Season 3 rules --- docs/SUBMISSION_DRAFT.md | 70 +++++++++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/docs/SUBMISSION_DRAFT.md b/docs/SUBMISSION_DRAFT.md index 82a9171..13f5c4f 100644 --- a/docs/SUBMISSION_DRAFT.md +++ b/docs/SUBMISSION_DRAFT.md @@ -1,37 +1,69 @@ -# CommitLedger - submission draft +# CommitLedger — HackCanton Season 3 Submission Draft -**Status: draft; do not submit until FINAL_CHECKLIST.md is complete.** Final portal fields and length limits must be confirmed by the registered participant. +**Status: draft. Do not submit until `docs/FINAL_CHECKLIST.md` has no critical implementation failures and all external account/publication gates are verified.** -## One-line description +## Track +**Track 1 — Real-World Assets (RWA) & Business Workflows** -CommitLedger binds verified GitHub work to a role-authorized bounty workflow and an auditable settlement receipt on Canton. +## One-line description +CommitLedger turns verified GitHub contribution work into an authorization-controlled Canton settlement workflow with an auditable receipt. -## Problem and approach +## Problem +GitHub can prove that an issue exists and a pull request merged, but it does not by itself enforce which party may verify a bounty claim or which party may settle it. -Open-source bounty participants need a consistent way to connect accepted work with an inspectable settlement decision. CommitLedger reads a real GitHub issue and pull request server-side, validates the repository, issue reference, contributor policy, target branch and merged commit, and binds that evidence to a Daml lifecycle. +## Solution +CommitLedger binds a real GitHub issue and merged pull request to a Daml state machine: -The implemented flow is Issue -> Bounty -> ClaimRequest -> ClaimedBounty -> SubmittedBounty -> VerifiedBounty -> SettlementReceipt. The maintainer, contributor and verifier have different choices. The verifier remains a trusted external oracle: Daml does not independently call GitHub. Hashing makes the evidence identifiable; it does not eliminate trust in that oracle. +Issue → Bounty → Claim → Pull Request → Merge Verification → Settlement → SettlementReceipt. -## Why Canton and Daml +The backend independently validates repository identity, issue binding, contributor policy, base branch, merged state, head SHA, merge commit SHA, and merge evidence before the ledger workflow advances. -The intended demonstrated value is authorized state progression, inspectable contract/update references, and prevention of re-exercising the same consumed verified contract. That last property is per-contract replay protection, not a claim that an issue can never receive multiple separately created bounties. Demo runs intentionally have distinct identifiers. +## Why Canton +Canton/Daml is used for meaningful workflow state and authorization: distinct maintainer, contributor, and verifier roles; authorized contract choices; inspectable update/contract identifiers; and consumed-contract replay protection. GitHub remains the external evidence source. -## Technical implementation +## Target users / ICP +Open-source maintainers, foundations, ecosystem grant programs, and organizations operating contributor bounty workflows. -Node.js backend and local judge UI; canonical GitHub API adapter; Daml contracts; Canton JSON Ledger API v2; proof export; automated Node/Daml verification and local proof scripts. The project has no application-package dependency on a paid hosting provider or external payment rail. +## GTM +1. prove the workflow in one repository; +2. pilot with one foundation/ecosystem bounty operator; +3. after validation, expand repository support and integrate an approved settlement adapter. -## Evidence currently established +No paid pilot or customer traction is claimed today. -The original Node test suite passed 16/16 locally. The expanded delivery suite passed 65/65 locally, including failure classification, canonical GitHub validation, local HTTP-server checks and simulated lifecycle transport. These transport tests do not establish live Canton deployment or settlement. +## Validation / evidence +Use only evidence captured for the exact submission commit: +- real GitHub issue and merged PR fixture; +- green Node test suite; +- green Daml build/script tests; +- real Canton lifecycle to SettlementReceipt; +- exact negative rejection evidence; +- browser-accessible judge flow and exported proof. -## Runtime evidence to insert only after a successful run +Transport mocks are never described as live Canton evidence. -Insert the exact tested commit, DAR package ID, Canton environment, retained build/test logs, final receipt contract/update IDs, evidence JSON artifact, negative rejection responses, judge-accessible demo and video links. These artifacts are not yet claimed in this draft. No placeholder IDs or fabricated testimonials are supplied. +## Demo and pitch +The final recorded demo must be **5 minutes or less** and show: +1. problem and roles; +2. real GitHub issue/PR evidence; +3. authorized Canton lifecycle; +4. wrong-evidence / unauthorized / replay rejection; +5. final receipt and why Canton is necessary; +6. Track 1 business use and pilot path. -## Scope and integrity +## Integrity +DEMO_CREDIT is non-production test value. CommitLedger does not claim fiat/Canton Coin transfer, MainNet operation, production custody, real users, revenue, or organizer endorsement without evidence. -DEMO_CREDIT is non-production test value. This build does not claim fiat, Canton Coin or MainNet transfer; external users; production custody; financial returns; organizer endorsement; or guaranteed competition performance. The source fixture is the project's own real issue #5 and PR #6, not user adoption. +## AI disclosure +AI-assisted tools were used for research, planning, code review, tests, documentation, and implementation support. The team remains responsible for every submitted artifact and claim. -## Limitations and next production work +## Hackathon-period disclosure +Any pre-existing code must be disclosed. Only work completed during the official delivery phase (2026-09-18 through 2026-10-09) should be presented to judges as hackathon-period work. -Production identity/key separation, authenticated multi-organization operation, custody/payment integration, dispute handling, comprehensive adversarial contract review, monitoring, and audited deployment are outside this local demo. The current delivery's Daml/runtime compatibility remains to be proven on an executable Canton environment. +## Final links +Populate only when public and verified: +- Repository: +- Live/demo page: +- Demo video: +- Pitch: +- Evidence bundle: From 20b28309eac4df5e6f8bccb18cd4766e432ca673 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 19:39:55 +0600 Subject: [PATCH 013/144] docs: align final checklist with Season 3 gates --- docs/FINAL_CHECKLIST.md | 83 +++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 40 deletions(-) diff --git a/docs/FINAL_CHECKLIST.md b/docs/FINAL_CHECKLIST.md index 8a37a12..e6c6138 100644 --- a/docs/FINAL_CHECKLIST.md +++ b/docs/FINAL_CHECKLIST.md @@ -1,40 +1,43 @@ -# Final release and submission checklist - -A checked source-code item is not a checked runtime-evidence item. Keep proof attached to the exact delivery commit. - -## Completed in the delivery work - -- [x] Original Node suite executed (16 passed). -- [x] Expanded Node suite executed (65 passed). -- [x] Repository-bound issue references and regression cases implemented. -- [x] Canonical merge commit lookup, branch reachability and evidence binding implemented. -- [x] Network/auth/unknown errors excluded from ledger rejection proof. -- [x] Full lifecycle transport and local HTTP endpoint tests implemented/executed. -- [x] CI, local proof scripts, setup runbook, proof UI code and submission/video drafts prepared. - -## Technical release gates still open - -- [ ] Review and merge the delivery branch only after required checks succeed. -- [ ] Pinned DPM bundle installs in the actual runner environment. -- [ ] Current Daml compilation and Daml Script tests pass; retain logs. -- [ ] Fresh Canton starts, current DAR deploys, actual package ID is captured. -- [ ] Six real successful ledger transitions produce a matching final receipt. -- [ ] Wrong issue, unauthorized settlement and duplicate settlement return the expected ledger rejections. -- [ ] Reproduce from clean setup and check retries/failure cleanup. -- [ ] Review Daml authorization beyond happy-path source tests, including direct intermediate-template creation. -- [ ] Browser/mobile visual and interaction QA passes on actual permitted browser. -- [ ] No secrets in tracked files, logs, screenshots or video. -- [ ] Actual final video recorded and reviewed against retained proof. - -## Organizer/account gates still open - -- [ ] Registration and eligibility, solo/team status and prize eligibility confirmed from the actual account/portal. -- [ ] Deadline and timezone reverified from the official current rulebook. -- [ ] Required repository visibility, judge access and license checked. -- [ ] Pre-existing-code/build-period rules checked. -- [ ] Required network, sponsor technology and chosen-track rules checked. -- [ ] Video duration/format/hosting and live deployment requirements checked. -- [ ] Submission fields, intellectual-property terms and any identity/KYC requirements checked. -- [ ] Final submission reviewed by the participant and portal receipt retained. - -Do not close the live-Canton completion issue or mark final submission ready with any critical box still open. Repeated workflow attempts cannot override account restrictions or replace real evidence. +# HackCanton Season 3 Final Checklist + +A source-code check is not runtime proof. A runtime proof is not an account/portal proof. + +## Implementation gates +- [x] Repository-qualified issue matching implemented and Node-tested. +- [x] Merge commit SHA / branch reachability checks implemented and Node-tested. +- [x] Infrastructure errors excluded from security-rejection proof. +- [x] Competition-readiness PASS/BLOCKED/FAIL model implemented. +- [ ] Full current Node suite passes on final candidate commit. +- [ ] DPM 3.5.12 installs in the proof environment. +- [ ] Daml build passes and log is retained. +- [ ] Daml Script tests pass and log is retained. +- [ ] Current DAR is deployed to a real Canton runtime. +- [ ] Real Issue → Bounty → Claim → PR → Verify → Settle → Receipt flow succeeds. +- [ ] Wrong-evidence rejection is captured from Canton/Daml. +- [ ] Unauthorized settlement rejection is captured from Canton/Daml. +- [ ] Duplicate/replay settlement rejection is captured from Canton/Daml. +- [ ] Evidence bundle is coherent: one run ID + one source commit. +- [ ] Desktop/mobile judge flow passes browser QA. +- [ ] No secrets appear in tracked files, logs, screenshots, or video. +- [x] Track 1 business brief exists. +- [x] Pilot plan exists. +- [x] AI disclosure exists. +- [x] Hackathon work disclosure exists. +- [ ] Final ≤5-minute demo video is recorded and reviewed. + +## Official competition gates +- [x] Target track is Track 1 — RWA & Business Workflows. +- [x] Meaningful Canton integration is an explicit hard requirement in the design. +- [x] Submission deadline recorded as 2026-10-09 23:59 UTC. +- [ ] Public repository available to a logged-out judge. **Deferred by owner until final phase.** +- [ ] Public demo/video/pitch/project page accessible without requesting access. **Deferred until final phase.** +- [ ] Submitting account has burned/accrued the required 1,000 Mana. +- [ ] At least 10 days of required platform activity is satisfied. +- [ ] Project profile is complete. +- [ ] Journal is non-empty. +- [ ] Team/eligibility information is valid. +- [ ] Pre-existing code is disclosed and qualifying delivery-period work is identifiable. +- [ ] All final links tested in a private/logged-out browser window. +- [ ] Participant reviews final submission and retains portal receipt. + +Do not call CommitLedger submission-ready while any mandatory official gate above is unchecked. From caa98fc234e992da1294d0903bf767ba467235b5 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 20:09:24 +0600 Subject: [PATCH 014/144] feat: align judge UI with HackCanton scoring --- web/index.html | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/web/index.html b/web/index.html index e63dc0f..05079b3 100644 --- a/web/index.html +++ b/web/index.html @@ -6,9 +6,16 @@ CommitLedger - Verified work, settled on Canton - +
-

GITHUB EVIDENCE / DAML AUTHORIZATION / CANTON SETTLEMENT

Work is not settled until the proof is verifiable.

Bind a real GitHub issue and merged pull request to an authorization-controlled settlement receipt on Canton.

Issue→Bounty→Claim→Pull request→Verify→Settle
+

GITHUB EVIDENCE / DAML AUTHORIZATION / CANTON SETTLEMENT

Work is not settled until the proof is verifiable.

Bind a real GitHub issue and merged pull request to an authorization-controlled settlement receipt on Canton.

Issue→Bounty→Claim→Pull request→Verify→Settle→Receipt
+ +
+

COMPETITION READINESS

Submission stays fail-closed

BLOCKED · external gates pending
+

Implementation evidence can become verified independently. Repository publication, public judging links, Mana/activity, journal and project-profile requirements remain blocked until they are actually verified.

+
PASSNode verification baseline
BLOCKEDDaml + real Canton proof
BLOCKEDMana / 10-day activity / journal
BLOCKEDPublic judging materials
+
+

LIVE CANTON PROOF

Run the complete six-step lifecycle

Checking runtime

The ledger must be configured and reachable. No sample transaction IDs or simulated settlement success are displayed. DEMO_CREDIT is not real money.

@@ -16,7 +23,10 @@

No Canton lifecycle has been executed in this session.
+

CANONICAL ISSUE

Verify the work source

Waiting
Issue identity and state are fetched server-side from GitHub.

CANONICAL MERGE

Verify the merged pull request

Waiting
No merge evidence loaded.
+ +

OFFICIAL JUDGING CRITERIA

Six questions, six visible answers

Value / ProblemGitHub proves code events; Canton enforces who may verify and settle.
ICP / AudienceOpen-source maintainers, foundations and contributor bounty programs.
Metrics / ValidationReal GitHub fixture, deterministic tests and real Canton proof when captured.
GTM MaterialsSingle-repo pilot → foundation/ecosystem bounty program → settlement adapter.
MVP MaterialsGitHub verification + Daml authorization + Canton receipt + attack rejection.
Pitch MaterialsProblem → proof → rejected attacks → receipt → why Canton → pilot path.
CommitLedger is independent. DEMO_CREDIT is non-production test value. No mainnet, real-money or traction claim is made.
From b56952350482d3f3090a664f296eb21672603c3d Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 20:09:28 +0600 Subject: [PATCH 015/144] test: lock judge UI competition evidence --- tests/judge-ui-static.test.mjs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tests/judge-ui-static.test.mjs diff --git a/tests/judge-ui-static.test.mjs b/tests/judge-ui-static.test.mjs new file mode 100644 index 0000000..7d24a21 --- /dev/null +++ b/tests/judge-ui-static.test.mjs @@ -0,0 +1,22 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs/promises'; + +const html = await fs.readFile(new URL('../web/index.html', import.meta.url), 'utf8'); + +test('judge UI exposes competition readiness without claiming ready by default', () => { + assert.match(html, /id="competition-readiness"/); + assert.match(html, /BLOCKED/); + assert.doesNotMatch(html, /SUBMISSION READY/); +}); + +test('judge UI maps all six official judging criteria', () => { + for (const label of ['Value \/ Problem','ICP \/ Audience','Metrics \/ Validation','GTM Materials','MVP Materials','Pitch Materials']) { + assert.match(html, new RegExp(label)); + } +}); + +test('judge UI identifies Track 1 and keeps runtime proof evidence-first', () => { + assert.match(html, /Track 1/); + assert.match(html, /No sample transaction IDs or simulated settlement success/); +}); From 3c420168453ac68f9dd1bde57e63106e29aaaeb9 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 20:09:30 +0600 Subject: [PATCH 016/144] style: add readiness and criteria panels --- web/styles.css | 1 + 1 file changed, 1 insertion(+) diff --git a/web/styles.css b/web/styles.css index 0f05b83..c845f77 100644 --- a/web/styles.css +++ b/web/styles.css @@ -1 +1,2 @@ :root{font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#eef4f5;background:#061014;line-height:1.5}*{box-sizing:border-box}body{margin:0;background:radial-gradient(circle at 18% -8%,#153b43 0,transparent 32%),linear-gradient(#071216,#061014)}.nav{min-height:76px;display:flex;align-items:center;justify-content:space-between;max-width:1200px;margin:auto;padding:16px 24px;border-bottom:1px solid #1d3339;gap:12px}.brand{font-size:21px;font-weight:800;color:#fff;text-decoration:none;letter-spacing:-.035em}.nav-right{display:flex;gap:8px}.badge,.status{display:inline-flex;align-items:center;font-size:11px;border:1px solid #315058;border-radius:999px;padding:6px 10px;color:#b9ced3;background:#0b181d}main{max-width:1200px;margin:auto;padding:32px 24px 64px}.hero{max-width:1050px;padding:42px 0 48px}.eyebrow,.kicker{font-size:11px;letter-spacing:.15em;color:#91bac3;font-weight:800;margin:0 0 12px}.hero h1{font-size:clamp(40px,6.5vw,78px);line-height:1.03;letter-spacing:-.055em;margin:0}.lead{font-size:20px;color:#a9c0c6;max-width:820px;margin:24px 0}.flow{display:flex;flex-wrap:wrap;gap:9px;align-items:center;margin-top:28px}.flow span{padding:8px 11px;background:#0b171c;border:1px solid #254149;border-radius:9px;color:#a9bec3;font-size:13px}.flow b{color:#55767e}.workspace{display:grid;grid-template-columns:minmax(0,1.35fr) minmax(280px,.65fr);gap:18px;margin-bottom:18px}.panel{border:1px solid #203940;background:rgba(8,20,25,.88);border-radius:18px;padding:24px;min-width:0}.live-panel{margin-bottom:18px;border-color:#365f68;background:linear-gradient(135deg,rgba(11,31,36,.96),rgba(7,20,24,.9))}.panel-head{display:flex;justify-content:space-between;gap:18px;align-items:flex-start}.panel h2{font-size:26px;letter-spacing:-.03em;margin:0 0 18px}.metric{border-top:1px solid #213840;padding:14px 0;display:grid;gap:3px}.metric strong{font-size:14px}.metric span,.muted{color:#a0b6bc;font-size:14px}label{display:grid;gap:7px;font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:#a7bfc5;margin:12px 0;min-width:0}input{width:100%;min-width:0;border:1px solid #29474f;border-radius:10px;background:#061014;color:#eef5f6;padding:12px 13px;font:inherit;text-transform:none;letter-spacing:0}input:focus-visible,button:focus-visible{outline:2px solid #a3d6df;outline-offset:3px}input:disabled{opacity:.75}.row{display:grid;grid-template-columns:1fr 1fr;gap:12px}.row.three{grid-template-columns:.7fr 1.2fr .7fr}button{border:0;border-radius:10px;background:#e6f0f1;color:#061014;padding:12px 16px;font-weight:800;cursor:pointer;margin:6px 6px 0 0}button:disabled{cursor:not-allowed;opacity:.45}.secondary{background:#193740;color:#d8e9ed;border:1px solid #365963}.summary{display:grid;gap:4px;margin-top:18px;padding:15px;border:1px solid #2c4b53;border-radius:12px;background:#08171b}.output{min-height:150px;max-height:360px;overflow:auto;background:#040b0d;border:1px solid #1a3036;border-radius:10px;padding:14px;color:#b2d1d7;font-size:12px;white-space:pre-wrap;overflow-wrap:anywhere}.timeline{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin-top:22px}.timeline-empty,.timeline-error{grid-column:1/-1;padding:18px;border:1px dashed #315059;border-radius:12px;color:#a1b9bf}.timeline-error{border-color:#704047;color:#efbec2}.timeline-step{border:1px solid #29474e;border-radius:12px;padding:14px;background:#071519;min-width:0}.timeline-step strong,.timeline-step span{display:block}.timeline-step strong{font-size:14px;margin-bottom:5px}.timeline-step span{font-size:12px;color:#a1bac1}.timeline-step code,.receipt code{display:block;overflow-wrap:anywhere;font-size:11px;margin:5px 0;color:#c1dce2}details{margin-top:8px}summary{cursor:pointer;font-size:12px}.receipt{margin-top:16px;padding:20px;border:1px solid #47717a;border-radius:14px;background:rgba(15,38,44,.72);overflow-wrap:anywhere}.hidden{display:none}.receipt h3{margin:0 0 15px;font-size:22px}.receipt-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}.receipt-grid span,.receipt-grid strong{display:block}.receipt-grid span{font-size:11px;color:#a2bdc5}.receipt-grid strong{font-size:13px;margin-top:4px}.negative{border-top:1px solid #35565e;margin-top:12px;padding-top:12px}footer{max-width:1200px;margin:auto;padding:24px;border-top:1px solid #1d3339;color:#98b0b7;font-size:12px}@media(max-width:900px){.workspace,.timeline,.receipt-grid,.row,.row.three{grid-template-columns:1fr}.hero{padding-top:18px}.panel-head{display:block}.status{margin-bottom:10px}.panel{padding:20px}main{padding:20px 16px 40px}.nav{padding:14px 16px}.nav-right .badge:last-child{display:none}.eyebrow{line-height:1.8}.lead{font-size:17px}} +\n.readiness-panel,.criteria-panel{margin-bottom:18px}.status.blocked{border-color:#7a6540;color:#e4bc68}.readiness-grid,.criteria-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin-top:18px}.readiness-grid>div,.criteria-grid article{border:1px solid #29474f;border-radius:12px;padding:14px;background:#071519;min-width:0}.readiness-grid strong,.readiness-grid span,.criteria-grid strong,.criteria-grid span{display:block}.readiness-grid strong,.criteria-grid strong{font-size:13px;margin-bottom:5px}.readiness-grid span,.criteria-grid span{font-size:12px;color:#a1bac1}.criteria-grid{grid-template-columns:repeat(3,minmax(0,1fr))}@media(max-width:900px){.readiness-grid,.criteria-grid{grid-template-columns:1fr}} From 2545840cb057d9169a3b86c5d40d02a677a32a3e Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 20:14:46 +0600 Subject: [PATCH 017/144] docs: update evidence-based delivery status --- docs/DELIVERY_STATUS.md | 76 ++++++++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 23 deletions(-) diff --git a/docs/DELIVERY_STATUS.md b/docs/DELIVERY_STATUS.md index 570011d..c52273e 100644 --- a/docs/DELIVERY_STATUS.md +++ b/docs/DELIVERY_STATUS.md @@ -1,34 +1,64 @@ -# Delivery status - 29 September 2026 +# Delivery status — 29 September 2026 -Base: `27bd415c862fd220994739a618c7bafc8e181bd5`. Delivery branch: `fix/verified-delivery-20260929`. +Delivery branch: `fix/verified-delivery-20260929`. -## Executed successfully +## Verified evidence -The original five Node test files were reconstructed from the GitHub connector and checked against their Git blob hashes. The full original suite passed: **16 tests, 0 failures**, Node.js 22.16.0. After the changes, the expanded suite passed: **65 tests, 0 failures**. The expanded suite includes real local HTTP server tests and mocked provider/ledger transport tests; it is NOT live Canton proof. +- Original Node baseline: **16/16 passed** on the earlier delivery baseline. +- Expanded Node suite: **65/65 passed** on delivery commit `202693cbc10d835166fe3bf6240fa56e529ff1cd`. +- Competition-readiness evaluator was developed test-first in an isolated Node fixture: **4/4 passed** before pushing the same source/tests to this branch. +- Judge-UI static acceptance tests were developed test-first in an isolated fixture: **3/3 passed** before pushing the same markup/tests to this branch. +- Official HackCanton Season 3 Rules, Timeline, Tracks and Materials were rechecked on 2026-09-29. The binding competition design is `docs/superpowers/specs/2026-09-29-hackcanton-s3-competition-ready-design.md`. -## Implemented and Node-tested +These results do **not** establish that the full Node suite is green on the newest branch head. The latest head still needs a clean full-suite run. -- Repository-qualified issue binding rejects foreign repository references, foreign URLs and misleading Markdown labels. References in code/comments are excluded. This proves an explicit issue mention, not semantic completion of the issue. -- Merge commit SHA is required, independently looked up through the GitHub commit endpoint, checked for reachability from the target branch, and included in the evidence hash. -- Structured Canton errors are required for negative proofs. Network, unknown, authentication, throttling and unrelated errors do not count as authorization/replay evidence. -- Unique demo-run bounty identities and unambiguous active-contract lookup prevent selecting a previous demo receipt. -- Canonical GitHub checks finish before any ledger write. -- HTTP server blocks arbitrary browser ledger-command submission, checks origins/content types, and keeps configuration separate from actual ledger health. -- Proof UI has readable steps, full identifiers, explicit negative responses and JSON export. Its browser-level visual validation is still pending. +## Implemented -## Implemented but not runtime-verified +### Verification and security +- Repository-qualified GitHub issue references, canonical PR/merge-commit verification and target-branch reachability. +- Merge commit SHA propagated through merge evidence and SettlementReceipt. +- Structured Canton rejection handling: network, auth, timeout, throttling and unknown infrastructure errors cannot become security proof. +- Unique demo identities, unambiguous active-contract lookup and GitHub preflight before ledger writes. +- Server-side origin/content-type protection and no arbitrary browser command submission using server-held ledger credentials. -Daml merge-SHA propagation and additional negative assertion; pinned DPM bootstrap; build/test/deploy/party/user allocation scripts; fail-closed evidence checker; push/PR/manual GitHub Actions jobs; optional real-browser recording script. +### Competition readiness +- Fail-closed `PASS / BLOCKED / FAIL` competition-readiness engine. +- External/account/publication gates remain BLOCKED until actually verified. +- Verified implementation evidence from different run IDs or source commits is rejected as incoherent. +- Track 1 business brief, pilot plan, AI disclosure and hackathon-period work disclosure. +- Final checklist and submission draft aligned to the live Season 3 rules. -## Blocking evidence still missing +### Judge experience +- Web UI now exposes Track 1, competition-readiness state and all six official judging criteria. +- Runtime-sensitive states do not display fake transaction IDs or simulated success. +- Figma judge-flow file includes desktop and mobile evidence-first layouts. +- Linear project tracks Competition Compliance, Runtime Proof, Judge UX and Submission Package. -- DPM installation, current Daml build and Daml Script execution. The local execution environment has no DPM and cannot resolve the official SDK download host. `scripts/verify-all.sh` correctly exits 2 rather than inventing success. -- Actual Canton deployment, six ledger updates/contracts, final receipt and specific ledger rejection responses. -- Successful complete GitHub Actions run for this delivery commit. A configured workflow is not a successful run. -- Browser/mobile visual QA. The available browser rejected local navigation with `ERR_BLOCKED_BY_ADMINISTRATOR`; this restriction was not bypassed. -- Final recorded runtime demo video. Recording instructions and code are not a recorded video. -- Current official portal/rulebook verification and account registration/eligibility confirmation. Public portal retrieval did not expose the formal submission rules. Earlier deadline notes are historical, not a new official verification. +## Current blockers -The GitHub connection works. Remote Desktop Commander was suggested as a way to use an authorized development machine; it was not confirmed connected. No paid service, production deployment, permission broadening or repository-publication change was made. +### Runtime proof +- The connected Remote Desktop development computer is currently **offline**. Earlier inspection showed Git/Node/npm/WSL available, but DPM/Daml were not installed. +- Therefore DPM 3.5.12 installation, current Daml build/test, DAR deployment, real Canton lifecycle, SettlementReceipt and exact negative ledger rejection evidence have not been produced on the newest head. +- Full browser/mobile QA against the running app and the final ≤5-minute evidence-backed video remain pending. -Do not close issue #4, claim 100% completion, or submit as runtime-verified until the missing evidence is produced. This status takes precedence over older implementation-only wording in other repository documents. +### GitHub Actions +Latest workflow for head `3c420168453ac68f9dd1bde57e63106e29aaaeb9`: +- run `36580546442` +- jobs: `node`, `canton-proof`, `repository-gates` +- all completed with failure **before any recorded step ran** +- each job reports `steps: []`, `runner_id: 0`, and an empty runner name + +That result is not evidence of a test assertion failure. The exact GitHub platform/account-level cause is not exposed by the available connection, so do not label it billing, policy or code failure without evidence. + +### External competition gates intentionally deferred by owner +- make repository/public judging materials accessible; +- verify AppsFactory 1,000 Mana; +- verify minimum 10 days platform activity; +- verify journal/project profile/account eligibility; +- final portal submission and receipt. + +## Current release status + +The architecture, competition specification, business package, readiness automation, judge-flow design and web scoring UI are advanced, but CommitLedger is **not yet runtime-verified or submission-ready**. + +Keep PR #8 draft. Do not claim 100%, close the live-Canton evidence gate, merge based on source inspection alone, or submit until the real runtime and external gates are satisfied. From 8b16f412ce7ade000e8013105f653b71e4ac4ac7 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 20:14:50 +0600 Subject: [PATCH 018/144] docs: update evidence manifest for S3 delivery --- docs/EVIDENCE_MANIFEST.md | 94 ++++++++++++++++++++++++++------------- 1 file changed, 64 insertions(+), 30 deletions(-) diff --git a/docs/EVIDENCE_MANIFEST.md b/docs/EVIDENCE_MANIFEST.md index 0bd81c4..219ed18 100644 --- a/docs/EVIDENCE_MANIFEST.md +++ b/docs/EVIDENCE_MANIFEST.md @@ -1,35 +1,69 @@ # Evidence Manifest -## Already created +Evidence is accepted only when it is tied to the exact source commit and, for runtime artifacts, one coherent run ID. -- Real open GitHub issue #5. +## Established source / test evidence + +- Real GitHub issue #5. - Real merged GitHub pull request #6. - PR #6 references issue #5 without closing it, enabling reproducible bounty-source verification. -- Daml contract source implementing the role-separated authorization lifecycle. -- Daml revision/resubmission path. -- Daml negative-test source for unauthorized verification, bad merge evidence, wrong issue binding, unauthorized settlement, and replay. -- Canonical GitHub verifier with exact issue-reference validation. -- SHA-256 merge evidence that includes repository, issue, PR, URL, head SHA, base branch and merge timestamp. -- Zero-dependency Node domain / verifier / Canton command / orchestrator tests. -- Canton JSON Ledger API client with active-contract discovery. -- One-command full lifecycle orchestrator. -- Judge-facing web application with a real Canton proof timeline and final receipt view. -- Competition compliance and winning-standard gates. -- No paid hosting/API dependency. - -## Runtime evidence still required before final submission - -These cannot be truthfully marked complete until executed against a Canton/Daml runtime: - -- Daml compiler/build success. -- Daml Script test success. -- Canton package deployment. -- Real JSON Ledger API contract creation. -- End-to-end transitions through `SettlementReceipt`. -- Captured ledger update IDs / contract IDs. -- Captured authorization/replay failures. -- Recorded final demo video based on verified runtime. - -## Integrity note - -A missing runtime proof is a build task, not something to hide with screenshots, mock transaction IDs, fabricated users, or fake adoption. +- Daml role-separated settlement lifecycle source. +- Canonical GitHub verifier with repository-qualified issue binding, merge commit SHA validation and target-branch reachability checks. +- Structured ledger rejection classifier that excludes infrastructure/auth/throttling failures from security proof. +- Competition-readiness engine with PASS / BLOCKED / FAIL semantics and mixed-run/source-commit rejection. +- Track 1 business brief, pilot plan, AI disclosure and hackathon-period work disclosure. +- Web judge UI exposes official judging criteria and fail-closed readiness state. +- Figma desktop/mobile judge-flow design. +- Linear evidence tracker with compliance/runtime/UX/submission milestones. + +Historical executed test evidence: +- original Node suite: 16/16; +- expanded Node suite on commit `202693cbc10d835166fe3bf6240fa56e529ff1cd`: 65/65; +- readiness evaluator isolated TDD fixture: 4/4; +- judge-UI isolated TDD fixture: 3/3. + +The newest branch head still requires one clean full `npm test` run. + +## Runtime evidence required before product-ready + +All items below must be generated from one current source commit/run: + +- Node full-suite log. +- DPM 3.5.12 installation/version evidence. +- Daml compiler/build log. +- Daml Script test log. +- DAR/package identifier. +- Canton runtime/environment identifier. +- Distinct Maintainer / Contributor / Verifier parties. +- Real contract and update identifiers for the lifecycle. +- Final active `SettlementReceipt`. +- Canonical merge commit SHA and evidence hash in that receipt. +- Specific Daml/Canton rejection for wrong evidence. +- Specific authorization rejection for unauthorized settlement. +- Specific inactive/not-found rejection for duplicate settlement. +- Proof JSON containing run ID and source commit. +- Browser desktop/mobile QA against the real running app. +- Final recorded demo based on the retained proof. + +A timeout, connection error, HTTP 401/403, rate limit, parser error, CI runner failure, mocked transport or static screenshot is not runtime security evidence. + +## Competition submission evidence required later + +The owner intentionally deferred these final-phase actions: +- public repository; +- public demo/video/pitch/project page; +- logged-out/private-window link verification; +- AppsFactory 1,000 Mana; +- required ≥10 activity days; +- non-empty journal; +- complete project profile; +- eligibility/team confirmation; +- final portal submission receipt. + +## GitHub Actions note + +Workflow run `36580546442` for head `3c420168453ac68f9dd1bde57e63106e29aaaeb9` failed with zero recorded steps and no allocated runner (`runner_id: 0`) in all three jobs. It produced no application test/runtime evidence and must not be treated as a code-test result. + +## Integrity rule + +Missing evidence remains BLOCKED. Never replace it with fabricated transaction IDs, fake users, fake adoption, fake security rejections, or inferred runtime success. From 51aaca690f9e749e63e86c56e09ed573ba372cd6 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 21:36:10 +0600 Subject: [PATCH 019/144] feat: add top-tier evidence summary rail --- web/index.html | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/web/index.html b/web/index.html index 05079b3..97db0f5 100644 --- a/web/index.html +++ b/web/index.html @@ -8,7 +8,14 @@
-

GITHUB EVIDENCE / DAML AUTHORIZATION / CANTON SETTLEMENT

Work is not settled until the proof is verifiable.

Bind a real GitHub issue and merged pull request to an authorization-controlled settlement receipt on Canton.

Issue→Bounty→Claim→Pull request→Verify→Settle→Receipt
+

GITHUB EVIDENCE / DAML AUTHORIZATION / CANTON SETTLEMENT

Work is not settled until the proof is verifiable.

From merged code to inspectable settlement: canonical GitHub evidence, explicit multi-party authorization, and a Canton receipt in one judgeable workflow.

Issue→Bounty→Claim→Pull request→Verify→Settle→Receipt
+ +
+
SourceGitHub #5 → PR #6Canonical issue + merged pull request
+
Authorization3 explicit rolesMaintainer · Contributor · Verifier
+
SettlementDEMO_CREDITNon-production test value
+
RuntimeBLOCKEDWaiting for real Canton proof
+

COMPETITION READINESS

Submission stays fail-closed

BLOCKED · external gates pending
@@ -25,7 +32,7 @@

CANONICAL ISSUE

Verify the work source

Waiting
Issue identity and state are fetched server-side from GitHub.
-

CANONICAL MERGE

Verify the merged pull request

Waiting
No merge evidence loaded.
+

CANONICAL MERGE

Verify the merged pull request

Waiting
No merge evidence loaded.

OFFICIAL JUDGING CRITERIA

Six questions, six visible answers

Value / ProblemGitHub proves code events; Canton enforces who may verify and settle.
ICP / AudienceOpen-source maintainers, foundations and contributor bounty programs.
Metrics / ValidationReal GitHub fixture, deterministic tests and real Canton proof when captured.
GTM MaterialsSingle-repo pilot → foundation/ecosystem bounty program → settlement adapter.
MVP MaterialsGitHub verification + Daml authorization + Canton receipt + attack rejection.
Pitch MaterialsProblem → proof → rejected attacks → receipt → why Canton → pilot path.
CommitLedger is independent. DEMO_CREDIT is non-production test value. No mainnet, real-money or traction claim is made.
From e7fb92636f80f65497c245e34133a233ce509d6a Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 21:36:15 +0600 Subject: [PATCH 020/144] style: sharpen judge cockpit hierarchy --- web/styles.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/styles.css b/web/styles.css index c845f77..3a9980a 100644 --- a/web/styles.css +++ b/web/styles.css @@ -1,2 +1,4 @@ :root{font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#eef4f5;background:#061014;line-height:1.5}*{box-sizing:border-box}body{margin:0;background:radial-gradient(circle at 18% -8%,#153b43 0,transparent 32%),linear-gradient(#071216,#061014)}.nav{min-height:76px;display:flex;align-items:center;justify-content:space-between;max-width:1200px;margin:auto;padding:16px 24px;border-bottom:1px solid #1d3339;gap:12px}.brand{font-size:21px;font-weight:800;color:#fff;text-decoration:none;letter-spacing:-.035em}.nav-right{display:flex;gap:8px}.badge,.status{display:inline-flex;align-items:center;font-size:11px;border:1px solid #315058;border-radius:999px;padding:6px 10px;color:#b9ced3;background:#0b181d}main{max-width:1200px;margin:auto;padding:32px 24px 64px}.hero{max-width:1050px;padding:42px 0 48px}.eyebrow,.kicker{font-size:11px;letter-spacing:.15em;color:#91bac3;font-weight:800;margin:0 0 12px}.hero h1{font-size:clamp(40px,6.5vw,78px);line-height:1.03;letter-spacing:-.055em;margin:0}.lead{font-size:20px;color:#a9c0c6;max-width:820px;margin:24px 0}.flow{display:flex;flex-wrap:wrap;gap:9px;align-items:center;margin-top:28px}.flow span{padding:8px 11px;background:#0b171c;border:1px solid #254149;border-radius:9px;color:#a9bec3;font-size:13px}.flow b{color:#55767e}.workspace{display:grid;grid-template-columns:minmax(0,1.35fr) minmax(280px,.65fr);gap:18px;margin-bottom:18px}.panel{border:1px solid #203940;background:rgba(8,20,25,.88);border-radius:18px;padding:24px;min-width:0}.live-panel{margin-bottom:18px;border-color:#365f68;background:linear-gradient(135deg,rgba(11,31,36,.96),rgba(7,20,24,.9))}.panel-head{display:flex;justify-content:space-between;gap:18px;align-items:flex-start}.panel h2{font-size:26px;letter-spacing:-.03em;margin:0 0 18px}.metric{border-top:1px solid #213840;padding:14px 0;display:grid;gap:3px}.metric strong{font-size:14px}.metric span,.muted{color:#a0b6bc;font-size:14px}label{display:grid;gap:7px;font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:#a7bfc5;margin:12px 0;min-width:0}input{width:100%;min-width:0;border:1px solid #29474f;border-radius:10px;background:#061014;color:#eef5f6;padding:12px 13px;font:inherit;text-transform:none;letter-spacing:0}input:focus-visible,button:focus-visible{outline:2px solid #a3d6df;outline-offset:3px}input:disabled{opacity:.75}.row{display:grid;grid-template-columns:1fr 1fr;gap:12px}.row.three{grid-template-columns:.7fr 1.2fr .7fr}button{border:0;border-radius:10px;background:#e6f0f1;color:#061014;padding:12px 16px;font-weight:800;cursor:pointer;margin:6px 6px 0 0}button:disabled{cursor:not-allowed;opacity:.45}.secondary{background:#193740;color:#d8e9ed;border:1px solid #365963}.summary{display:grid;gap:4px;margin-top:18px;padding:15px;border:1px solid #2c4b53;border-radius:12px;background:#08171b}.output{min-height:150px;max-height:360px;overflow:auto;background:#040b0d;border:1px solid #1a3036;border-radius:10px;padding:14px;color:#b2d1d7;font-size:12px;white-space:pre-wrap;overflow-wrap:anywhere}.timeline{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin-top:22px}.timeline-empty,.timeline-error{grid-column:1/-1;padding:18px;border:1px dashed #315059;border-radius:12px;color:#a1b9bf}.timeline-error{border-color:#704047;color:#efbec2}.timeline-step{border:1px solid #29474e;border-radius:12px;padding:14px;background:#071519;min-width:0}.timeline-step strong,.timeline-step span{display:block}.timeline-step strong{font-size:14px;margin-bottom:5px}.timeline-step span{font-size:12px;color:#a1bac1}.timeline-step code,.receipt code{display:block;overflow-wrap:anywhere;font-size:11px;margin:5px 0;color:#c1dce2}details{margin-top:8px}summary{cursor:pointer;font-size:12px}.receipt{margin-top:16px;padding:20px;border:1px solid #47717a;border-radius:14px;background:rgba(15,38,44,.72);overflow-wrap:anywhere}.hidden{display:none}.receipt h3{margin:0 0 15px;font-size:22px}.receipt-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}.receipt-grid span,.receipt-grid strong{display:block}.receipt-grid span{font-size:11px;color:#a2bdc5}.receipt-grid strong{font-size:13px;margin-top:4px}.negative{border-top:1px solid #35565e;margin-top:12px;padding-top:12px}footer{max-width:1200px;margin:auto;padding:24px;border-top:1px solid #1d3339;color:#98b0b7;font-size:12px}@media(max-width:900px){.workspace,.timeline,.receipt-grid,.row,.row.three{grid-template-columns:1fr}.hero{padding-top:18px}.panel-head{display:block}.status{margin-bottom:10px}.panel{padding:20px}main{padding:20px 16px 40px}.nav{padding:14px 16px}.nav-right .badge:last-child{display:none}.eyebrow{line-height:1.8}.lead{font-size:17px}} -\n.readiness-panel,.criteria-panel{margin-bottom:18px}.status.blocked{border-color:#7a6540;color:#e4bc68}.readiness-grid,.criteria-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin-top:18px}.readiness-grid>div,.criteria-grid article{border:1px solid #29474f;border-radius:12px;padding:14px;background:#071519;min-width:0}.readiness-grid strong,.readiness-grid span,.criteria-grid strong,.criteria-grid span{display:block}.readiness-grid strong,.criteria-grid strong{font-size:13px;margin-bottom:5px}.readiness-grid span,.criteria-grid span{font-size:12px;color:#a1bac1}.criteria-grid{grid-template-columns:repeat(3,minmax(0,1fr))}@media(max-width:900px){.readiness-grid,.criteria-grid{grid-template-columns:1fr}} +.readiness-panel,.criteria-panel{margin-bottom:18px}.status.blocked{border-color:#7a6540;color:#e4bc68}.readiness-grid,.criteria-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin-top:18px}.readiness-grid>div,.criteria-grid article{border:1px solid #29474f;border-radius:12px;padding:14px;background:#071519;min-width:0}.readiness-grid strong,.readiness-grid span,.criteria-grid strong,.criteria-grid span{display:block}.readiness-grid strong,.criteria-grid strong{font-size:13px;margin-bottom:5px}.readiness-grid span,.criteria-grid span{font-size:12px;color:#a1bac1}.criteria-grid{grid-template-columns:repeat(3,minmax(0,1fr))}@media(max-width:900px){.readiness-grid,.criteria-grid{grid-template-columns:1fr}} + +.evidence-rail{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin:0 0 18px}.evidence-rail article{border:1px solid #29474f;border-radius:14px;padding:16px;background:linear-gradient(180deg,rgba(11,24,29,.94),rgba(7,21,25,.9));min-width:0}.evidence-rail span,.evidence-rail strong,.evidence-rail small{display:block}.evidence-rail span{font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:#91bac3;font-weight:800}.evidence-rail strong{font-size:18px;letter-spacing:-.02em;margin:6px 0 3px;color:#eef4f5}.evidence-rail small{font-size:12px;color:#a0b6bc}.evidence-rail .runtime-blocked{border-color:#7a6540}.evidence-rail .runtime-blocked strong{color:#e4bc68}@media(max-width:900px){.evidence-rail{grid-template-columns:1fr 1fr}}@media(max-width:560px){.evidence-rail{grid-template-columns:1fr}} From 51295b89bba262290a4ec5471c6d4ada8d35d332 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 21:36:18 +0600 Subject: [PATCH 021/144] test: lock proof summary rail --- tests/judge-ui-static.test.mjs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/judge-ui-static.test.mjs b/tests/judge-ui-static.test.mjs index 7d24a21..344b78b 100644 --- a/tests/judge-ui-static.test.mjs +++ b/tests/judge-ui-static.test.mjs @@ -20,3 +20,10 @@ test('judge UI identifies Track 1 and keeps runtime proof evidence-first', () => assert.match(html, /Track 1/); assert.match(html, /No sample transaction IDs or simulated settlement success/); }); + +test('judge cockpit exposes source authorization settlement and runtime summary', () => { + for (const label of ['Source','Authorization','Settlement','Runtime']) { + assert.match(html, new RegExp(label)); + } + assert.match(html, /Runtime[\s\S]*BLOCKED/); +}); From d6e2313be601de7cc1c49f242e091c6c08bfaa6e Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 21:36:29 +0600 Subject: [PATCH 022/144] docs: record UI UX benchmark synthesis --- docs/UI_UX_BENCHMARKS.md | 83 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 docs/UI_UX_BENCHMARKS.md diff --git a/docs/UI_UX_BENCHMARKS.md b/docs/UI_UX_BENCHMARKS.md new file mode 100644 index 0000000..58cd53a --- /dev/null +++ b/docs/UI_UX_BENCHMARKS.md @@ -0,0 +1,83 @@ +# CommitLedger UI/UX benchmark set + +CommitLedger is a hybrid of four product categories: +1. developer workflow / source-of-truth tools; +2. bounty and contributor-workflow products; +3. audit/compliance and evidence systems; +4. on-chain / deployment operations dashboards. + +The design should borrow interaction principles, not copy any single product. + +## Benchmark products + +### GitHub +Use for: +- source-of-truth issue/PR identity; +- close connection between work items and commits/merges; +- compact status and reference formatting; +- drill-down from summary to evidence. + +Do not copy GitHub's repository chrome or exact issue layout. + +### Linear +Use for: +- calm density; +- strong hierarchy with little decorative noise; +- predictable status language; +- fast scanning and keyboard-friendly mental model. + +Do not copy Linear's sidebar, typography scale, or issue screen one-for-one. + +### Stripe Dashboard +Use for: +- inspectable identifiers; +- event/log detail; +- clear distinction between successful business state and diagnostic state; +- copyable technical evidence. + +Do not imitate Stripe branding or payment-specific visual metaphors. + +### Vercel +Use for: +- deployment/runtime status clarity; +- environment/run separation; +- commit-bound evidence; +- logs/errors as first-class diagnostic material. + +Do not copy Vercel's deployment page layout. + +### Algora +Use for: +- GitHub-native bounty context; +- compact bounty state; +- issue-linked contributor workflow. + +Do not copy its bounty list or payout presentation. + +### Gitcoin +Use conceptually for: +- grants/bounty ecosystem framing; +- ecosystem/operator context; +- transparent program state. + +Do not copy grant-round visual design. + +## CommitLedger synthesis + +The resulting product should feel like a **proof cockpit for developer settlements**: +- top summary rail: Source / Authorization / Settlement / Runtime; +- primary lifecycle: Issue → Bounty → Claim → PR → Verify → Settle → Receipt; +- exact IDs and hashes stay inspectable; +- runtime and competition readiness are visibly distinct; +- negative security proof is a first-class section; +- no runtime proof means BLOCKED, never decorative success; +- judge can understand the product in 10–15 seconds and verify it deeply in 60–90 seconds. + +## Visual direction + +- dark operational surface, high legibility; +- restrained accent color; +- borders and density inspired by developer tools, not finance marketing; +- large value statement, compact operational cards; +- no gradients or decoration that reduce evidence readability; +- mobile becomes a vertical proof narrative, not a squeezed desktop dashboard. From 2a9623ec639068f4de25bbf1876b45271611638d Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 21:44:27 +0600 Subject: [PATCH 023/144] build: add cloud proof devcontainer --- .devcontainer/devcontainer.json | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..f219e85 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,32 @@ +{ + "name": "CommitLedger Canton Proof", + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "ghcr.io/devcontainers/features/node:1": { + "version": "22" + }, + "ghcr.io/devcontainers/features/java:1": { + "version": "21" + }, + "ghcr.io/devcontainers/features/python:1": { + "version": "3.12" + } + }, + "postCreateCommand": "bash scripts/bootstrap-codespace.sh", + "forwardPorts": [4173, 3975], + "portsAttributes": { + "4173": { + "label": "CommitLedger judge UI", + "onAutoForward": "notify" + }, + "3975": { + "label": "Canton JSON Ledger API", + "onAutoForward": "silent" + } + }, + "customizations": { + "vscode": { + "extensions": [] + } + } +} From a27d869db67a89c4cde8c567461a53439cfea25b Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 21:44:31 +0600 Subject: [PATCH 024/144] build: add Codespaces bootstrap --- scripts/bootstrap-codespace.sh | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 scripts/bootstrap-codespace.sh diff --git a/scripts/bootstrap-codespace.sh b/scripts/bootstrap-codespace.sh new file mode 100644 index 0000000..7f2468d --- /dev/null +++ b/scripts/bootstrap-codespace.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "[CommitLedger] Preparing cloud development environment" +node --version +java -version +python3 --version + +bash scripts/bootstrap-dpm.sh + +if ! grep -Fq '.dpm/bin' "$HOME/.bashrc" 2>/dev/null; then + printf '\nexport PATH="$HOME/.dpm/bin:$PATH"\n' >> "$HOME/.bashrc" +fi +export PATH="$HOME/.dpm/bin:$PATH" + +echo "[CommitLedger] Running Node verification" +npm test + +echo +echo "[CommitLedger] Environment ready." +echo "For the real proof run:" +echo " export GITHUB_TOKEN=" +echo " bash scripts/run-local-proof.sh" +echo +echo "To keep the verified UI open afterward:" +echo " COMMITLEDGER_SERVE_AFTER_PROOF=true bash scripts/run-local-proof.sh" From 2d6326b6e9df8e309c37eb67cf2ce32c210867af Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 21:44:36 +0600 Subject: [PATCH 025/144] docs: document PC-free proof paths --- docs/CLOUD_EXECUTION.md | 64 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 docs/CLOUD_EXECUTION.md diff --git a/docs/CLOUD_EXECUTION.md b/docs/CLOUD_EXECUTION.md new file mode 100644 index 0000000..fb449f1 --- /dev/null +++ b/docs/CLOUD_EXECUTION.md @@ -0,0 +1,64 @@ +# Cloud execution — no always-on local PC required + +CommitLedger does **not** fundamentally depend on Remote Desktop Commander. + +A real Canton/Daml proof only needs a Linux-capable environment that can: +- run Node.js 22; +- run Java 21; +- install the pinned DPM bundle; +- access GitHub and Digital Asset download endpoints; +- keep local ports available while the proof runs. + +## Preferred automation order + +### 1. GitHub Actions — zero-touch target + +This is the preferred final state. A push/PR should execute Node tests, Daml build/tests, a fresh Canton sandbox, evidence capture and repository gates automatically. + +At the moment the repository's hosted jobs are failing before a runner is allocated (`steps: []`, `runner_id: 0`). That is an infrastructure/account-level blocker until GitHub actually assigns a runner. Do not treat it as a failing test. + +When hosted Actions begins allocating runners, no personal computer or Remote Desktop session is needed. + +### 2. GitHub Codespaces — easiest manual fallback + +The repository contains `.devcontainer/devcontainer.json`. + +Open the repository in a Codespace and let the container bootstrap. It installs: +- Node 22; +- Java 21; +- Python 3; +- pinned DPM via `scripts/bootstrap-dpm.sh`; +- then runs the Node test suite. + +After creation, run: + +```bash +export GITHUB_TOKEN=... +bash scripts/run-local-proof.sh +``` + +If the repository becomes public and the fixture is public, the GitHub token requirement may be removable depending on GitHub API/rate-limit needs. + +To keep the judge UI open after a successful proof: + +```bash +COMMITLEDGER_SERVE_AFTER_PROOF=true bash scripts/run-local-proof.sh +``` + +Codespaces usage/availability depends on the GitHub account and its configured quota/billing. CommitLedger does not change billing settings automatically. + +### 3. Remote Desktop Commander — optional fallback + +Use only when a local development machine is convenient. It is useful because the agent can execute commands and inspect the running browser, but it is not an architectural dependency. + +The device must be online only while the agent is actively executing local commands. It does not need to remain connected after evidence has been captured and retained. + +## One coherent proof rule + +Regardless of environment, the final evidence must come from: +- one source commit; +- one run ID; +- one real Canton runtime; +- retained Node/Daml/runtime logs. + +Never combine a Node log from one commit with a Canton proof from another and mark the result green. From 541592f3835c8faffdb2f5fab3eb7879af3c47da Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 21:44:53 +0600 Subject: [PATCH 026/144] docs: add Track 1 pitch --- docs/PITCH.md | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 docs/PITCH.md diff --git a/docs/PITCH.md b/docs/PITCH.md new file mode 100644 index 0000000..85daf9e --- /dev/null +++ b/docs/PITCH.md @@ -0,0 +1,64 @@ +# HackCanton Season 3 — CommitLedger Pitch + +## 15-second version + +CommitLedger turns a verified GitHub contribution into an authorization-controlled Canton settlement receipt. GitHub proves the work event; Daml controls who may advance and settle the workflow; Canton preserves the auditable state and replay-safe receipt. + +## Problem + +Open-source bounty workflows often stop at "the PR merged." That proves a code event, but not an auditable multi-party settlement process with explicit authority over verification and settlement. + +## Product + +CommitLedger binds: +`Issue → Bounty → Claim → Pull Request → Verification → SettlementReceipt`. + +Three explicit roles participate: +- Maintainer; +- Contributor; +- Verifier. + +The backend independently checks canonical GitHub evidence before settlement state can advance. + +## Why Canton + +Without Canton/Daml, this product loses its core: +- explicit party authorization; +- stateful contractual transitions; +- consumed-contract replay protection; +- inspectable contract/update identifiers; +- final receipt bound to external evidence. + +GitHub remains the source of truth for repository events. Canton is the source of truth for authorized settlement workflow state. + +## Track 1 business case + +Primary ICP: +- open-source maintainers; +- foundations; +- ecosystem bounty programs; +- organizations paying external contributors. + +Likely buyer: +the organization operating the contributor/bounty program. + +Pilot: +1. one repository and maintainer; +2. one foundation/ecosystem campaign; +3. after validation, add an approved production settlement adapter. + +No paid customer, revenue, production transfer or adoption claim is made today. + +## Validation + +Use only executed evidence: +- real GitHub issue and merged PR fixture; +- full Node test result for the submission commit; +- Daml build and script tests; +- real Canton lifecycle; +- wrong-evidence, unauthorized-settlement and replay rejection; +- exported receipt/proof JSON. + +## Close + +CommitLedger is not "blockchain attached to GitHub." It separates evidence from authority: GitHub proves what happened, the verifier attests the canonical evidence, and Canton/Daml controls what each party is allowed to do next. From de09e46a2d85866e23d88b41ed2c1e31cc5cd5dd Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 21:44:56 +0600 Subject: [PATCH 027/144] docs: lock <=5 minute evidence demo --- docs/DEMO_SCRIPT.md | 72 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 12 deletions(-) diff --git a/docs/DEMO_SCRIPT.md b/docs/DEMO_SCRIPT.md index d07c5e4..5731c54 100644 --- a/docs/DEMO_SCRIPT.md +++ b/docs/DEMO_SCRIPT.md @@ -1,27 +1,75 @@ -# Demo script - evidence first +# Demo script — evidence first -Status: script prepared; actual verified-runtime video NOT yet recorded. Suggested internal length: about three minutes, adjustable to the official portal requirement, which is not yet verified. +**Official Season 3 limit: 5 minutes maximum.** +Target runtime: **3:30–4:15**, leaving safety margin. + +Status: script prepared; actual verified-runtime video NOT yet recorded. ## Preparation -Run the entire automated runbook on the delivery commit. Do not film a simulated lifecycle or reuse old IDs. Keep the successful real Canton session open. Hide tokens, terminals containing secrets and unrelated private information. Make GitHub fixture access available to the authorized judge without publishing credentials. +Run the automated proof on the exact delivery commit. Do not film a simulated lifecycle or reuse IDs from another commit/run. Keep the successful Canton session open. Hide tokens, terminals containing secrets and unrelated private information. + +## Suggested timeline + +### 0:00–0:25 — Problem +"A merged pull request proves that code was accepted, but it does not by itself provide a multi-party settlement workflow. CommitLedger connects canonical GitHub evidence to role-authorized Daml transitions on Canton." + +Show the top proof summary: +**Source / Authorization / Settlement / Runtime**. + +### 0:25–0:55 — Real source +Show GitHub issue #5 and merged PR #6, including the explicit issue reference and canonical merge commit SHA. + +State clearly: +- this is a reproducible demo fixture; +- it is not external traction; +- issue-reference validation does not claim semantic proof that the code solved the issue. -## Storyboard and narration +### 0:55–1:20 — Roles +Show Maintainer, Contributor and Verifier. -**Problem (opening):** "A merged pull request proves that code was accepted, but it does not by itself provide an auditable bounty settlement workflow. CommitLedger connects canonical GitHub evidence to role-authorized Daml transitions on Canton." +Explain: +- Maintainer creates/accepts/settles; +- Contributor claims/submits; +- Verifier attests canonical merge evidence; +- Canton/Daml controls authorized transitions. -**Real source:** Show issue #5 and merged PR #6, including the explicit issue reference. Explain that this is an internal demo fixture, not external traction. Show the canonical merge commit SHA. Explain that issue mention verification does not evaluate whether the code solves the issue. +DEMO_CREDIT is test value, not money or Canton Coin. -**Roles:** Show Maintainer, Contributor and Verifier. Explain who creates, claims, accepts, submits, verifies and settles. Clearly label this as a local test ledger with DEMO_CREDIT, not money or Canton Coin. +### 1:20–2:30 — Live lifecycle +Click **Run live Canton lifecycle** only after health reports the real ledger reachable. -**Live flow:** Click "Run live Canton lifecycle". Wait for the real backend response. Expand a full contract/update ID and the final settlement receipt. Match its merge SHA and evidence hash to the GitHub proof. Download the JSON proof. +Show: +Issue → Bounty → Claim → PR → Verify → Settle → Receipt. -**Failures:** Expand the three rejection responses: wrong issue evidence, contributor attempting settlement, and replay of the consumed verified contract. Explain the exact error code and relevant assertion/contract ID. Do not count a network error as a rejected unauthorized transaction. +Expand at least one complete update ID and contract ID. Show the final SettlementReceipt, merge commit SHA and evidence hash. Export the proof JSON. -**Reproducibility:** Show the tested commit, Node/Daml logs and automated runbook. Say only what the retained logs actually prove. +### 2:30–3:15 — Negative security proof +Show all three exact rejections: +1. wrong issue evidence; +2. contributor attempting settlement; +3. replay/duplicate settlement. -**Close:** "GitHub supplies external work state; the verifier is a trusted oracle; Daml restricts the recorded transitions; Canton provides the inspectable receipt. Production custody, identity management and payment rails remain outside this demo." +Explain the actual Daml/Canton code/assertion. A network error, 401/403, timeout or rate-limit response is never presented as authorization proof. + +### 3:15–3:45 — Why Canton +"GitHub supplies external work state. The verifier is the evidence oracle. Daml controls the permitted state transitions. Canton provides the inspectable workflow state and final receipt." + +### 3:45–4:15 — Track 1 / business close +Primary users: maintainers, foundations and bounty programs. + +Pilot: +one repository → one ecosystem/foundation program → production settlement adapter after validation. + +End on the receipt/proof summary, not a marketing screen. ## Before publishing -Confirm the official duration, file format and hosting rules. Watch the complete video, check IDs against the proof file, remove secrets, verify judge access, and keep the unedited technical capture alongside the final video. Do not present a storyboard or silent capture as an already accepted submission. +- confirm every displayed ID belongs to the final source commit/run; +- remove secrets and unrelated private information; +- verify audio and legibility; +- keep total length ≤5:00; +- verify judge-accessible links in a logged-out/private browser; +- retain the unedited technical capture and evidence bundle. + +Do not present this storyboard as an already recorded or accepted submission. From 8ebdf7c307346fdd66a439f34eb76389ba0b1388 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 22:24:18 +0600 Subject: [PATCH 028/144] fix: wait for Canton synchronizer readiness before party allocation --- scripts/run-local-proof.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/run-local-proof.sh b/scripts/run-local-proof.sh index 7065a9f..4840060 100755 --- a/scripts/run-local-proof.sh +++ b/scripts/run-local-proof.sh @@ -4,7 +4,7 @@ cd "$(dirname "$0")/.." export PATH="${DPM_HOME:-$HOME/.dpm}/bin:$PATH" export DPM_SDK_VERSION=3.5.12 mkdir -p evidence -rm -f evidence/canton-proof.json evidence/ledger-setup.json +rm -f evidence/canton-proof.json evidence/ledger-setup.json evidence/ledger-api.port evidence/json-api.port bash scripts/verify-all.sh DAR="$PWD/daml/.daml/dist/commit-ledger-0.1.0.dar" test -f "$DAR" @@ -16,7 +16,9 @@ if curl -s --max-time 2 --connect-timeout 2 "$CANTON_JSON_API_URL/v2/state/ledge echo 'Port 3975 is already in use; stop that service before creating a fresh proof.' >&2 exit 2 fi -dpm sandbox --json-api-port 3975 --dar "$DAR" > evidence/canton-sandbox.log 2>&1 & +# DPM's port files are written only when the corresponding sandbox services are ready. +# Waiting for them avoids racing party allocation against synchronizer connection. +dpm sandbox --json-api-port 3975 --json-api-port-file evidence/json-api.port --port-file evidence/ledger-api.port --dar "$DAR" > evidence/canton-sandbox.log 2>&1 & sandbox_pid=$! trap 'kill "$sandbox_pid" 2>/dev/null || true; wait "$sandbox_pid" 2>/dev/null || true' EXIT ready=false @@ -25,13 +27,13 @@ for attempt in $(seq 1 180); do tail -n 60 evidence/canton-sandbox.log exit 1 fi - if curl --fail --silent --max-time 2 "$CANTON_JSON_API_URL/v2/state/ledger-end" > evidence/ledger-end.json; then + if test -s evidence/ledger-api.port && test -s evidence/json-api.port && curl --fail --silent --max-time 2 "$CANTON_JSON_API_URL/v2/state/ledger-end" > evidence/ledger-end.json; then ready=true break fi sleep 1 done -if [ "$ready" != true ]; then echo 'Canton readiness check failed' >&2; exit 1; fi +if [ "$ready" != true ]; then echo 'Canton readiness check failed' >&2; tail -n 80 evidence/canton-sandbox.log >&2 || true; exit 1; fi node scripts/bootstrap-local.mjs set -a source .env.canton-demo.local From 4dd838eafcd09578c068dabd16e142dac26d624a Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 22:24:21 +0600 Subject: [PATCH 029/144] test: guard Canton sandbox readiness ordering --- tests/sandbox-readiness.test.mjs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/sandbox-readiness.test.mjs diff --git a/tests/sandbox-readiness.test.mjs b/tests/sandbox-readiness.test.mjs new file mode 100644 index 0000000..ffc3774 --- /dev/null +++ b/tests/sandbox-readiness.test.mjs @@ -0,0 +1,14 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs/promises'; + +const script = await fs.readFile(new URL('../scripts/run-local-proof.sh', import.meta.url), 'utf8'); + +test('waits for DPM sandbox ready port-file before allocating parties', () => { + assert.match(script, /--port-file\s+[^\n]*ledger-api\.port/); + assert.match(script, /--json-api-port-file\s+[^\n]*json-api\.port/); + const waitIndex = script.indexOf('test -s evidence/ledger-api.port'); + const bootstrapIndex = script.indexOf('node scripts/bootstrap-local.mjs'); + assert.ok(waitIndex >= 0, 'must wait for non-empty ledger-api.port'); + assert.ok(bootstrapIndex > waitIndex, 'party bootstrap must happen after sandbox ready signal'); +}); From 3b2fa49e0270a8a18915c548ee6f0fe403381f95 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 22:36:24 +0600 Subject: [PATCH 030/144] fix: retry only transient synchronizer party allocation --- scripts/bootstrap-local.mjs | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/scripts/bootstrap-local.mjs b/scripts/bootstrap-local.mjs index 756d263..0f71c53 100644 --- a/scripts/bootstrap-local.mjs +++ b/scripts/bootstrap-local.mjs @@ -1,19 +1,42 @@ // This bootstrap ONLY targets an explicitly unauthenticated loopback demo ledger. import {writeFile} from 'node:fs/promises'; +import {setTimeout as delay} from 'node:timers/promises'; import {randomUUID} from 'node:crypto'; import {CantonJsonApi} from '../src/canton-json-api.mjs'; +import {CantonApiError} from '../src/ledger-errors.mjs'; + const baseUrl=process.env.CANTON_JSON_API_URL || 'http://127.0.0.1:3975'; const api=new CantonJsonApi({baseUrl,insecureLocal:true}); const suffix=randomUUID().slice(0,8); const env={CANTON_JSON_API_URL:baseUrl,CANTON_INSECURE_LOCAL:'true',CANTON_PACKAGE_ID:process.env.CANTON_PACKAGE_ID}; if(!/^[a-f0-9]{64}$/.test(env.CANTON_PACKAGE_ID||'')) throw new Error('Actual DAR package ID is required'); + +async function allocateParty(role) { + const body=JSON.stringify({partyIdHint:`CommitLedger-${role}-${suffix}`,identityProviderId:''}); + let lastError; + for(let attempt=1; attempt<=30; attempt+=1) { + try { + const result=await api.request('/v2/parties',{method:'POST',body}); + const party=result.partyDetails?.party; + if(!party) throw new Error(`Party allocation did not return partyDetails.party for ${role}`); + return party; + } catch(error) { + lastError=error; + const synchronizerRace=error instanceof CantonApiError && error.code==='PARTY_ALLOCATION_WITHOUT_CONNECTED_SYNCHRONIZER'; + if(!synchronizerRace || attempt===30) throw error; + await delay(1000); + } + } + throw lastError; +} + const parties=[]; for(const role of ['MAINTAINER','CONTRIBUTOR','VERIFIER']) { - const result=await api.request('/v2/parties',{method:'POST',body:JSON.stringify({partyIdHint:`CommitLedger-${role}-${suffix}`,identityProviderId:''})}); - const party=result.partyDetails?.party; - if(!party) throw new Error(`Party allocation did not return partyDetails.party for ${role}`); - env[`CANTON_${role}_PARTY`]=party;parties.push(party); + const party=await allocateParty(role); + env[`CANTON_${role}_PARTY`]=party; + parties.push(party); } + const userId=`commitledger-demo-${suffix}`; await api.request('/v2/users',{method:'POST',body:JSON.stringify({ user:{id:userId,primaryParty:parties[0],identityProviderId:'',isDeactivated:false}, @@ -22,7 +45,7 @@ await api.request('/v2/users',{method:'POST',body:JSON.stringify({ ]) })}); env.CANTON_USER_ID=userId; -// Shell single-quote escaping. No bearer tokens are created or written here. + const quote=value=>"'"+String(value).replaceAll("'","'\\''")+"'"; await writeFile('.env.canton-demo.local',Object.entries(env).map(([k,v])=>`${k}=${quote(v)}`).join('\n')+'\n',{mode:0o600}); await writeFile('evidence/ledger-setup.json',JSON.stringify({environment:'local-sandbox-no-auth',packageId:env.CANTON_PACKAGE_ID,parties,userId},null,2)+'\n'); From 591a1a6b996056e43dac0e262a8252a800fe9f2b Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 22:36:29 +0600 Subject: [PATCH 031/144] fix: bind verification to exact source commit --- scripts/verify-all.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/verify-all.sh b/scripts/verify-all.sh index f5f2884..b2cd284 100755 --- a/scripts/verify-all.sh +++ b/scripts/verify-all.sh @@ -5,6 +5,7 @@ mkdir -p evidence rm -f evidence/verification.json export PATH="${DPM_HOME:-$HOME/.dpm}/bin:$PATH" export DPM_SDK_VERSION=3.5.12 +SOURCE_COMMIT=${GITHUB_SHA:-$(git rev-parse HEAD)} node --version | tee evidence/node-version.log npm test 2>&1 | tee evidence/node-tests.log if ! command -v dpm >/dev/null 2>&1; then @@ -14,4 +15,4 @@ fi (cd daml && dpm version --active) 2>&1 | tee evidence/dpm-version.log (cd daml && dpm build) 2>&1 | tee evidence/daml-build.log (cd daml && dpm test) 2>&1 | tee evidence/daml-tests.log -node -e 'require("node:fs").writeFileSync("evidence/verification.json",JSON.stringify({node:true,damlBuild:true,damlTests:true,verifiedAt:new Date().toISOString(),commit:process.env.GITHUB_SHA||"local-working-tree"},null,2)+"\n")' +SOURCE_COMMIT="$SOURCE_COMMIT" node -e 'require("node:fs").writeFileSync("evidence/verification.json",JSON.stringify({node:true,damlBuild:true,damlTests:true,verifiedAt:new Date().toISOString(),commit:process.env.SOURCE_COMMIT},null,2)+"\n")' From d07e69162e5ba8d785699cc9caa1a40ca7c22262 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 22:36:33 +0600 Subject: [PATCH 032/144] fix: reject mixed-commit evidence bundles --- scripts/check-evidence.mjs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/check-evidence.mjs b/scripts/check-evidence.mjs index d1852d5..815879b 100644 --- a/scripts/check-evidence.mjs +++ b/scripts/check-evidence.mjs @@ -1,9 +1,14 @@ import {readFile} from 'node:fs/promises'; import {buildMergeEvidence} from '../src/github-verifier.mjs'; + const verification=JSON.parse(await readFile('evidence/verification.json','utf8')); if(!verification.node || !verification.damlBuild || !verification.damlTests) throw new Error('Full build/test verification is missing'); +if(!/^[a-f0-9]{40}$/.test(verification.commit||'')) throw new Error('Verification must be bound to an exact Git commit'); + const proof=JSON.parse(await readFile('evidence/canton-proof.json','utf8')); if(proof.schemaVersion!==2 || !/^[a-f0-9]{64}$/.test(proof.packageId||'')) throw new Error('Real package ID and current proof schema are required'); +if(proof.sourceCommit!==verification.commit) throw new Error('Node/Daml verification and Canton proof must use the same source commit'); + const names=['BOUNTY_ON_LEDGER','CLAIM_REQUESTED','CLAIMED','PR_SUBMITTED','VERIFIED','SETTLED']; if(proof.steps?.length!==6 || proof.steps.some((s,i)=>s.name!==names[i] || !s.updateId || !s.contractId || !Number.isSafeInteger(s.completionOffset))) throw new Error('Six complete ledger transitions are required'); if(new Set(proof.steps.map(s=>s.contractId)).size!==6) throw new Error('Contract IDs must be distinct'); @@ -12,4 +17,4 @@ if(proof.settlementReceipt?.rewardUnit!=='DEMO_CREDIT') throw new Error('Expecte const hash=buildMergeEvidence(proof.mergeEvidence).evidenceHash; if(hash!==proof.mergeEvidence.evidenceHash || hash!==proof.settlementReceipt.evidenceHash) throw new Error('Receipt evidence hash mismatch'); if(proof.settlementReceipt.mergeCommitSha!==proof.mergeEvidence.mergeCommitSha) throw new Error('Receipt merge commit mismatch'); -console.log('Technical evidence structure passes. This does not independently authenticate the ledger or clear organizer/video/submission gates.'); +console.log('Technical evidence structure passes and is bound to one source commit. This does not independently authenticate the ledger or clear organizer/video/submission gates.'); From 25670223f2a1f988554d0b3e4672b240ade33594 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 22:36:37 +0600 Subject: [PATCH 033/144] test: cover one-command sandbox lifecycle hardening --- tests/sandbox-readiness.test.mjs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/tests/sandbox-readiness.test.mjs b/tests/sandbox-readiness.test.mjs index ffc3774..95bede8 100644 --- a/tests/sandbox-readiness.test.mjs +++ b/tests/sandbox-readiness.test.mjs @@ -4,11 +4,22 @@ import fs from 'node:fs/promises'; const script = await fs.readFile(new URL('../scripts/run-local-proof.sh', import.meta.url), 'utf8'); -test('waits for DPM sandbox ready port-file before allocating parties', () => { - assert.match(script, /--port-file\s+[^\n]*ledger-api\.port/); - assert.match(script, /--json-api-port-file\s+[^\n]*json-api\.port/); - const waitIndex = script.indexOf('test -s evidence/ledger-api.port'); +test('uses only the DPM 3.5.12 canton-port-file readiness flag', () => { + assert.match(script, /--canton-port-file\s+evidence\/canton-ports\.json/); + assert.doesNotMatch(script, /--port-file\b/); + assert.doesNotMatch(script, /--json-api-port-file\b/); +}); + +test('waits for Canton ready signal before allocating parties', () => { + const waitIndex = script.indexOf('test -s evidence/canton-ports.json'); const bootstrapIndex = script.indexOf('node scripts/bootstrap-local.mjs'); - assert.ok(waitIndex >= 0, 'must wait for non-empty ledger-api.port'); - assert.ok(bootstrapIndex > waitIndex, 'party bootstrap must happen after sandbox ready signal'); + assert.ok(waitIndex >= 0); + assert.ok(bootstrapIndex > waitIndex); +}); + +test('refuses to force-kill unrelated listeners and records owned sandbox pid', () => { + assert.match(script, /canton-open-source/); + assert.match(script, /cannot prove it owns/); + assert.match(script, /canton-sandbox\.pid/); + assert.doesNotMatch(script, /kill -9/); }); From 64bfe3ed6fcd41bc464f14306f2d336f73af5972 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 22:37:05 +0600 Subject: [PATCH 034/144] test: require DPM 3.5.12 compatible dynamic sandbox startup --- tests/sandbox-readiness.test.mjs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/tests/sandbox-readiness.test.mjs b/tests/sandbox-readiness.test.mjs index 95bede8..5d4c22f 100644 --- a/tests/sandbox-readiness.test.mjs +++ b/tests/sandbox-readiness.test.mjs @@ -4,22 +4,20 @@ import fs from 'node:fs/promises'; const script = await fs.readFile(new URL('../scripts/run-local-proof.sh', import.meta.url), 'utf8'); -test('uses only the DPM 3.5.12 canton-port-file readiness flag', () => { +test('uses the DPM 3.5.12 canton-port-file readiness flag only', () => { assert.match(script, /--canton-port-file\s+evidence\/canton-ports\.json/); assert.doesNotMatch(script, /--port-file\b/); assert.doesNotMatch(script, /--json-api-port-file\b/); }); +test('chooses a free loopback JSON API port per run', () => { + assert.match(script, /socket\.bind\(\("127\.0\.0\.1",0\)\)/); + assert.match(script, /CANTON_JSON_API_URL="http:\/\/127\.0\.0\.1:\$CANTON_JSON_API_PORT"/); +}); + test('waits for Canton ready signal before allocating parties', () => { const waitIndex = script.indexOf('test -s evidence/canton-ports.json'); const bootstrapIndex = script.indexOf('node scripts/bootstrap-local.mjs'); - assert.ok(waitIndex >= 0); - assert.ok(bootstrapIndex > waitIndex); -}); - -test('refuses to force-kill unrelated listeners and records owned sandbox pid', () => { - assert.match(script, /canton-open-source/); - assert.match(script, /cannot prove it owns/); - assert.match(script, /canton-sandbox\.pid/); - assert.doesNotMatch(script, /kill -9/); + assert.ok(waitIndex >= 0, 'must wait for canton-port-file'); + assert.ok(bootstrapIndex > waitIndex, 'party bootstrap must happen after ready signal'); }); From d9d8b33516209aa9a1f4d7b7de4a5d04d37fd6ab Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 22:37:25 +0600 Subject: [PATCH 035/144] fix: make Canton proof startup collision-free on DPM 3.5.12 --- scripts/run-local-proof.sh | 70 +++++++++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/scripts/run-local-proof.sh b/scripts/run-local-proof.sh index 4840060..fa452d0 100755 --- a/scripts/run-local-proof.sh +++ b/scripts/run-local-proof.sh @@ -1,48 +1,86 @@ #!/usr/bin/env bash set -euo pipefail cd "$(dirname "$0")/.." + export PATH="${DPM_HOME:-$HOME/.dpm}/bin:$PATH" export DPM_SDK_VERSION=3.5.12 mkdir -p evidence -rm -f evidence/canton-proof.json evidence/ledger-setup.json evidence/ledger-api.port evidence/json-api.port +rm -f evidence/canton-proof.json evidence/ledger-setup.json evidence/canton-ports.json evidence/ledger-end.json + +SOURCE_COMMIT=$(git rev-parse HEAD) +export GITHUB_SHA="${GITHUB_SHA:-$SOURCE_COMMIT}" + +if ! git diff --quiet || ! git diff --cached --quiet; then + echo 'Tracked working-tree changes detected. Commit or stash them before generating competition evidence.' >&2 + exit 2 +fi + +if [ -z "${GITHUB_TOKEN:-}" ] && command -v gh >/dev/null 2>&1; then + GITHUB_TOKEN=$(gh auth token 2>/dev/null || true) + export GITHUB_TOKEN +fi + bash scripts/verify-all.sh + DAR="$PWD/daml/.daml/dist/commit-ledger-0.1.0.dar" test -f "$DAR" + export CANTON_PACKAGE_ID CANTON_PACKAGE_ID=$(python3 scripts/dar-package-id.py "$DAR") -export CANTON_JSON_API_URL=http://127.0.0.1:3975 -# Refuse to reuse an unrelated server. This script owns its sandbox process. -if curl -s --max-time 2 --connect-timeout 2 "$CANTON_JSON_API_URL/v2/state/ledger-end" >/dev/null; then - echo 'Port 3975 is already in use; stop that service before creating a fresh proof.' >&2 - exit 2 -fi -# DPM's port files are written only when the corresponding sandbox services are ready. -# Waiting for them avoids racing party allocation against synchronizer connection. -dpm sandbox --json-api-port 3975 --json-api-port-file evidence/json-api.port --port-file evidence/ledger-api.port --dar "$DAR" > evidence/canton-sandbox.log 2>&1 & + +CANTON_JSON_API_PORT=$(python3 - <<'PY' +import socket +with socket.socket() as s: + s.bind(("127.0.0.1",0)) + print(s.getsockname()[1]) +PY +) +export CANTON_JSON_API_PORT +export CANTON_JSON_API_URL="http://127.0.0.1:$CANTON_JSON_API_PORT" + +echo "Using fresh Canton JSON API port $CANTON_JSON_API_PORT" + +dpm sandbox --json-api-port "$CANTON_JSON_API_PORT" --canton-port-file evidence/canton-ports.json --dar "$DAR" > evidence/canton-sandbox.log 2>&1 & sandbox_pid=$! -trap 'kill "$sandbox_pid" 2>/dev/null || true; wait "$sandbox_pid" 2>/dev/null || true' EXIT + +cleanup() { + if kill -0 "$sandbox_pid" 2>/dev/null; then + kill "$sandbox_pid" 2>/dev/null || true + fi + wait "$sandbox_pid" 2>/dev/null || true +} +trap cleanup EXIT INT TERM + ready=false for attempt in $(seq 1 180); do if ! kill -0 "$sandbox_pid" 2>/dev/null; then - tail -n 60 evidence/canton-sandbox.log + echo 'Canton sandbox exited before becoming ready.' >&2 + tail -n 100 evidence/canton-sandbox.log >&2 || true exit 1 fi - if test -s evidence/ledger-api.port && test -s evidence/json-api.port && curl --fail --silent --max-time 2 "$CANTON_JSON_API_URL/v2/state/ledger-end" > evidence/ledger-end.json; then + if test -s evidence/canton-ports.json && curl --fail --silent --max-time 2 "$CANTON_JSON_API_URL/v2/state/ledger-end" > evidence/ledger-end.json; then ready=true break fi sleep 1 done -if [ "$ready" != true ]; then echo 'Canton readiness check failed' >&2; tail -n 80 evidence/canton-sandbox.log >&2 || true; exit 1; fi + +if [ "$ready" != true ]; then + echo 'Canton readiness check failed after 180 seconds.' >&2 + tail -n 100 evidence/canton-sandbox.log >&2 || true + exit 1 +fi + node scripts/bootstrap-local.mjs + set -a source .env.canton-demo.local set +a export COMMITLEDGER_EVIDENCE_FILE="$PWD/evidence/canton-proof.json" -# GITHUB_TOKEN is inherited for this private repository; it is never written to .env.canton-demo.local. + npm run demo:full 2>&1 | tee evidence/canton-demo.log node scripts/check-evidence.mjs + if [ "${COMMITLEDGER_SERVE_AFTER_PROOF:-false}" = true ]; then - # Foreground operator session; Ctrl+C also cleans up the owned sandbox. npm start fi From 83fb7468e3f51e2fc1cf202d41bd40b79429df1e Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 22:37:48 +0600 Subject: [PATCH 036/144] fix: retry transient synchronizer party allocation --- scripts/bootstrap-local.mjs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/scripts/bootstrap-local.mjs b/scripts/bootstrap-local.mjs index 0f71c53..7e29e22 100644 --- a/scripts/bootstrap-local.mjs +++ b/scripts/bootstrap-local.mjs @@ -12,22 +12,20 @@ const env={CANTON_JSON_API_URL:baseUrl,CANTON_INSECURE_LOCAL:'true',CANTON_PACKA if(!/^[a-f0-9]{64}$/.test(env.CANTON_PACKAGE_ID||'')) throw new Error('Actual DAR package ID is required'); async function allocateParty(role) { - const body=JSON.stringify({partyIdHint:`CommitLedger-${role}-${suffix}`,identityProviderId:''}); - let lastError; + const requestBody=JSON.stringify({partyIdHint:`CommitLedger-${role}-${suffix}`,identityProviderId:''}); for(let attempt=1; attempt<=30; attempt+=1) { try { - const result=await api.request('/v2/parties',{method:'POST',body}); + const result=await api.request('/v2/parties',{method:'POST',body:requestBody}); const party=result.partyDetails?.party; if(!party) throw new Error(`Party allocation did not return partyDetails.party for ${role}`); return party; } catch(error) { - lastError=error; - const synchronizerRace=error instanceof CantonApiError && error.code==='PARTY_ALLOCATION_WITHOUT_CONNECTED_SYNCHRONIZER'; - if(!synchronizerRace || attempt===30) throw error; + const transient=error instanceof CantonApiError && error.code==='PARTY_ALLOCATION_WITHOUT_CONNECTED_SYNCHRONIZER'; + if(!transient || attempt===30) throw error; await delay(1000); } } - throw lastError; + throw new Error(`Party allocation retry loop exhausted for ${role}`); } const parties=[]; @@ -41,7 +39,8 @@ const userId=`commitledger-demo-${suffix}`; await api.request('/v2/users',{method:'POST',body:JSON.stringify({ user:{id:userId,primaryParty:parties[0],identityProviderId:'',isDeactivated:false}, rights:parties.flatMap(party=>[ - {kind:{CanActAs:{value:{party}}}}, {kind:{CanReadAs:{value:{party}}}} + {kind:{CanActAs:{value:{party}}}}, + {kind:{CanReadAs:{value:{party}}}} ]) })}); env.CANTON_USER_ID=userId; From b89012b51cfa994887f9b1409db03bd87f3f5eb6 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 22:38:10 +0600 Subject: [PATCH 037/144] fix: bind verification to exact source commit From 5ed43bdd6602ae9176684f3bc641bff0bf352595 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 22:38:14 +0600 Subject: [PATCH 038/144] fix: reject mixed-commit evidence bundles From 3af8b3ce032ef9d7abe4a73025b67995e8360c0c Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 22:40:03 +0600 Subject: [PATCH 039/144] test: lock transient party allocation retry --- tests/bootstrap-local-static.test.mjs | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 tests/bootstrap-local-static.test.mjs diff --git a/tests/bootstrap-local-static.test.mjs b/tests/bootstrap-local-static.test.mjs new file mode 100644 index 0000000..68a5901 --- /dev/null +++ b/tests/bootstrap-local-static.test.mjs @@ -0,0 +1,12 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs/promises'; + +const source = await fs.readFile(new URL('../scripts/bootstrap-local.mjs', import.meta.url), 'utf8'); + +test('party bootstrap retries only the known synchronizer connection race', () => { + assert.match(source, /PARTY_ALLOCATION_WITHOUT_CONNECTED_SYNCHRONIZER/); + assert.match(source, /attempt<=30/); + assert.match(source, /await delay\(1000\)/); + assert.doesNotMatch(source, /catch\s*\([^)]*\)\s*\{\s*await delay/); +}); From 32ff7a85d872e0208cd1ad45e4a573b251bebf9c Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 22:40:06 +0600 Subject: [PATCH 040/144] test: lock exact-commit evidence coherence --- tests/evidence-coherence-static.test.mjs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/evidence-coherence-static.test.mjs diff --git a/tests/evidence-coherence-static.test.mjs b/tests/evidence-coherence-static.test.mjs new file mode 100644 index 0000000..7178c4a --- /dev/null +++ b/tests/evidence-coherence-static.test.mjs @@ -0,0 +1,14 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs/promises'; + +const checker = await fs.readFile(new URL('../scripts/check-evidence.mjs', import.meta.url), 'utf8'); +const verifier = await fs.readFile(new URL('../scripts/verify-all.sh', import.meta.url), 'utf8'); +const runner = await fs.readFile(new URL('../scripts/run-local-proof.sh', import.meta.url), 'utf8'); + +test('verification and Canton proof must be bound to the same exact commit', () => { + assert.match(verifier, /git rev-parse HEAD/); + assert.match(checker, /proof\.sourceCommit!==verification\.commit/); + assert.match(checker, /\^\[a-f0-9\]\{40\}\$/); + assert.match(runner, /export GITHUB_SHA/); +}); From 54d3a36661bc7b98b2d39ae9ea13405174bc7771 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 22:41:18 +0600 Subject: [PATCH 041/144] fix: force proof source commit to checked-out HEAD From e2992a3258d5dbde002703264b0d6d86a9fb69e6 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 22:41:21 +0600 Subject: [PATCH 042/144] fix: verify exact checked-out commit --- scripts/verify-all.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/verify-all.sh b/scripts/verify-all.sh index b2cd284..357e565 100755 --- a/scripts/verify-all.sh +++ b/scripts/verify-all.sh @@ -5,7 +5,7 @@ mkdir -p evidence rm -f evidence/verification.json export PATH="${DPM_HOME:-$HOME/.dpm}/bin:$PATH" export DPM_SDK_VERSION=3.5.12 -SOURCE_COMMIT=${GITHUB_SHA:-$(git rev-parse HEAD)} +SOURCE_COMMIT=$(git rev-parse HEAD) node --version | tee evidence/node-version.log npm test 2>&1 | tee evidence/node-tests.log if ! command -v dpm >/dev/null 2>&1; then From 83f88ae5c424d5acc1d355c0ecbda87e77b93333 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 22:41:46 +0600 Subject: [PATCH 043/144] fix: bind proof to current HEAD --- scripts/run-local-proof.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/run-local-proof.sh b/scripts/run-local-proof.sh index fa452d0..a324fef 100755 --- a/scripts/run-local-proof.sh +++ b/scripts/run-local-proof.sh @@ -8,7 +8,7 @@ mkdir -p evidence rm -f evidence/canton-proof.json evidence/ledger-setup.json evidence/canton-ports.json evidence/ledger-end.json SOURCE_COMMIT=$(git rev-parse HEAD) -export GITHUB_SHA="${GITHUB_SHA:-$SOURCE_COMMIT}" +export GITHUB_SHA="$SOURCE_COMMIT" if ! git diff --quiet || ! git diff --cached --quiet; then echo 'Tracked working-tree changes detected. Commit or stash them before generating competition evidence.' >&2 From ca54431cff55cd3a607123c6c8429669b3e5032e Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 22:41:55 +0600 Subject: [PATCH 044/144] fix: verify the checked-out source commit From 5cc4602037c7617be6070b158a26b32a0f79e314 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 22:42:07 +0600 Subject: [PATCH 045/144] test: prevent stale source commit labels --- tests/evidence-coherence-static.test.mjs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/evidence-coherence-static.test.mjs b/tests/evidence-coherence-static.test.mjs index 7178c4a..5f1f1d1 100644 --- a/tests/evidence-coherence-static.test.mjs +++ b/tests/evidence-coherence-static.test.mjs @@ -6,9 +6,10 @@ const checker = await fs.readFile(new URL('../scripts/check-evidence.mjs', impor const verifier = await fs.readFile(new URL('../scripts/verify-all.sh', import.meta.url), 'utf8'); const runner = await fs.readFile(new URL('../scripts/run-local-proof.sh', import.meta.url), 'utf8'); -test('verification and Canton proof must be bound to the same exact commit', () => { - assert.match(verifier, /git rev-parse HEAD/); +test('verification and Canton proof are forced onto the exact checked-out commit', () => { + assert.match(verifier, /SOURCE_COMMIT=\$\(git rev-parse HEAD\)/); + assert.match(runner, /SOURCE_COMMIT=\$\(git rev-parse HEAD\)/); + assert.match(runner, /export GITHUB_SHA="\$SOURCE_COMMIT"/); assert.match(checker, /proof\.sourceCommit!==verification\.commit/); assert.match(checker, /\^\[a-f0-9\]\{40\}\$/); - assert.match(runner, /export GITHUB_SHA/); }); From 17a199b14d39a06b9bf4659359a33a35becfe134 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 22:43:02 +0600 Subject: [PATCH 046/144] docs: make proof runner one-command and collision-free --- docs/AUTOMATED_RUNBOOK.md | 89 +++++++++++++++++++++++++++++---------- 1 file changed, 67 insertions(+), 22 deletions(-) diff --git a/docs/AUTOMATED_RUNBOOK.md b/docs/AUTOMATED_RUNBOOK.md index 437b73d..60e5aa2 100644 --- a/docs/AUTOMATED_RUNBOOK.md +++ b/docs/AUTOMATED_RUNBOOK.md @@ -1,24 +1,51 @@ # Automated verification and real Canton proof +## Preferred execution + +Use GitHub Actions when hosted runners allocate correctly. If hosted Actions is unavailable, use the repository Codespace/devcontainer. Remote Desktop is optional. + +The proof runner is designed as a one-command path: + +```bash +bash scripts/run-local-proof.sh +``` + +It now: +- binds evidence to the exact checked-out Git commit; +- reruns the full Node suite; +- compiles/tests Daml with DPM 3.5.12; +- extracts the package identifier from the fresh DAR; +- chooses a free loopback JSON API port for each run, avoiding stale-port collisions; +- starts a fresh Canton sandbox with the DPM 3.5.12-compatible `--canton-port-file` ready signal; +- waits for both Canton readiness and the JSON ledger endpoint; +- retries only the known transient `PARTY_ALLOCATION_WITHOUT_CONNECTED_SYNCHRONIZER` race; +- allocates three parties and a demo user; +- obtains a Codespaces/GitHub CLI token automatically when `GITHUB_TOKEN` is not already present; +- runs the real GitHub-to-ledger lifecycle; +- captures structured negative rejections; +- rejects mixed-commit evidence bundles. + ## Prerequisites -Use an internet-connected Linux/macOS development machine with Node.js 22+, Java 21, Python 3, curl and Bash. Clone the private repository with your normal authenticated GitHub tooling, then check out `fix/verified-delivery-20260929`. Do not paste credentials into chat or commit them. +Use an internet-connected Linux environment with Node.js 22+, Java 21, Python 3, curl and Bash. The repository devcontainer supplies these dependencies and installs the pinned DPM bundle. + +Do not paste credentials into chat or commit them. -The existing fixture is private: provide `GITHUB_TOKEN` through your shell/session secret store with read access to this repository. GitHub Actions uses its repository-scoped token. No paid API or hosting is required by these scripts. +## Commands -## Local commands +For a new Codespace, bootstrap is normally automatic. If needed: ```bash -npm test -bash scripts/bootstrap-dpm.sh -bash scripts/run-local-proof.sh +bash scripts/bootstrap-codespace.sh ``` -The last command reruns Node tests, compiles/tests Daml, extracts the package identifier from the built DAR, starts a fresh owned Canton sandbox, uploads that DAR, allocates three actual parties and a demo user, runs the real GitHub-to-ledger lifecycle, and validates the evidence structure. It stops on any failure. It refuses to reuse a server already listening on port 3975 and stops only the sandbox process it started. Default Canton ports 6865-6869 must also be free. +Then run the proof: -`scripts/verify-all.sh` removes any stale verification marker before starting. `run-local-proof.sh` also removes stale final proof/setup files. Inspect log timestamps and the source commit; never mix artifacts from different runs. +```bash +bash scripts/run-local-proof.sh +``` -The generated `.env.canton-demo.local` contains local party IDs, package ID and user ID, NOT a bearer token. It is ignored by Git. It never replaces a personal `.env.local`. This environment is deliberately unauthenticated and loopback-only. Never expose it publicly; it is not production key management or an authenticated multi-organization deployment. +The script stops on the first unproven condition and prints the relevant runtime log tail. ## Keep a verified session open for the judge @@ -26,9 +53,30 @@ The generated `.env.canton-demo.local` contains local party IDs, package ID and COMMITLEDGER_SERVE_AFTER_PROOF=true bash scripts/run-local-proof.sh ``` -Only after the proof command succeeds does the UI start at `http://127.0.0.1:4173`. Leave this operator process running during the recording. Ctrl+C shuts down its sandbox. Each UI run creates a NEW demo bounty; repeated demo runs are not repeated payments of one contract. +Only after proof validation succeeds does the UI stay open. In Codespaces, open forwarded port 4173 from the Ports panel. + +Each UI run creates a new demo bounty. DEMO_CREDIT remains non-production test value. + +## Evidence + +Preserve these artifacts from the same run: +- `node-tests.log` +- `daml-build.log` +- `daml-tests.log` +- `verification.json` +- `canton-ports.json` +- `ledger-setup.json` +- `canton-proof.json` +- `canton-demo.log` +- `canton-sandbox.log` -For an existing authenticated development ledger, export `.env.example` variables with the actual deployed package, three distinct parties, authorized role tokens, and ledger user ID, then run: +`verification.json.commit` and `canton-proof.json.sourceCommit` must match exactly. + +A timeout, network error, authentication error, rate limit, malformed command, mocked transport, or CI runner failure is never accepted as Daml authorization/security evidence. + +## Existing authenticated ledger + +For a real authenticated development ledger, export the values in `.env.example` and run: ```bash export COMMITLEDGER_EVIDENCE_FILE=./evidence/canton-proof.json @@ -36,19 +84,17 @@ npm run demo:full npm start ``` -The application does not automatically load `.env` files. Export the variables in the shell. The newly added Daml record fields require a fresh build/deployment. This delivery does not claim an in-place upgrade/migration of old contracts. - -## Evidence +The application does not automatically source environment files. -Preserve `node-tests.log`, `daml-build.log`, `daml-tests.log`, `verification.json`, `ledger-setup.json`, `canton-proof.json` and `canton-demo.log` from the same run. Negative evidence must contain recognized ledger codes and the relevant assertion/contract details. A timeout or HTTP authentication error is not a security rejection. `check-evidence.mjs` checks structure only; genuine provenance comes from the recorded API responses and rerunning against the ledger. +## GitHub Actions -## Automated CI +The workflow contains Node, Canton proof and repository gate jobs. If GitHub assigns a hosted runner, no personal computer or Codespace is required. -The workflow has Node, Canton-proof and repository-gate jobs and triggers on main/fix branch pushes, pull requests and manual runs. It retains log/evidence artifacts even when a job fails. A private repository's Actions availability and account policies still apply. No billing or visibility setting is changed by this workflow. +The previously observed `steps: []` + `runner_id: 0` failure occurs before application steps execute and therefore is not a code-test result. -## Optional browser video capture +## Browser/video -Install Playwright only as a development tool outside the application dependencies: +After a verified runtime is open: ```bash python3 -m pip install playwright @@ -56,13 +102,12 @@ python3 -m playwright install chromium python3 scripts/record-demo.py ``` -Run this from a second terminal while the verified operator session above is still open. The recorder calls the actual UI, waits for a real successful API response and only retains a video when receipt/rejection checks succeed. Failed captures are discarded. The output is a silent technical walkthrough, not a finished narrated submission video. Check the organizer's format/duration rules before editing or submitting it. +The recorder retains a technical capture only when the live lifecycle, receipt and negative-proof checks succeed. The final submission video must remain ≤5 minutes and be reviewed before publishing. ## Technical references - https://docs.digitalasset.com/build/3.4/component-howtos/application-development/dpm-sandbox.html -- https://docs.digitalasset.com/build/3.4/explanations/json-api/index.html - https://docs.digitalasset.com/build/3.5/reference/json-api/openapi.html - https://archived.docs.digitalasset.com/operate/3.5/reference/error_codes.html -The pinned project bundle remains 3.5.12. These references guide API/command shapes; only a successful run with the pinned bundle establishes compatibility. +The pinned DPM SDK remains 3.5.12. Only an executed proof establishes final compatibility. From 002f87691da3605ca9438af4ff835318b4a3a124 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 22:48:43 +0600 Subject: [PATCH 047/144] test: match actual dynamic port allocator --- tests/sandbox-readiness.test.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/sandbox-readiness.test.mjs b/tests/sandbox-readiness.test.mjs index 5d4c22f..13ba697 100644 --- a/tests/sandbox-readiness.test.mjs +++ b/tests/sandbox-readiness.test.mjs @@ -11,7 +11,7 @@ test('uses the DPM 3.5.12 canton-port-file readiness flag only', () => { }); test('chooses a free loopback JSON API port per run', () => { - assert.match(script, /socket\.bind\(\("127\.0\.0\.1",0\)\)/); + assert.match(script, /s\.bind\(\("127\.0\.0\.1",0\)\)/); assert.match(script, /CANTON_JSON_API_URL="http:\/\/127\.0\.0\.1:\$CANTON_JSON_API_PORT"/); }); From 5de06014d5fc393f81f171c288cd08eb3be5dce1 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 22:54:26 +0600 Subject: [PATCH 048/144] test: require string encoding for Daml Int values --- tests/canton-commands.test.mjs | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/tests/canton-commands.test.mjs b/tests/canton-commands.test.mjs index 4d6bb46..64788fc 100644 --- a/tests/canton-commands.test.mjs +++ b/tests/canton-commands.test.mjs @@ -2,12 +2,13 @@ import test from "node:test"; import assert from "node:assert/strict"; import { buildCreateBountyCommand, + buildSubmitPullRequestCommand, buildReturnForRevisionCommand, buildVerifyMergedCommand, buildSettleCommand } from "../src/canton-commands.mjs"; -test("builds a Daml Bounty create command with the exact template", () => { +test("builds a Daml Bounty create command with Int fields encoded as strings", () => { const command = buildCreateBountyCommand({ packageId: "pkg123", maintainer: "Maintainer::1", @@ -23,9 +24,25 @@ test("builds a Daml Bounty create command with the exact template", () => { } }); assert.equal(command.CreateCommand.templateId, "pkg123:CommitLedger:Bounty"); + assert.equal(command.CreateCommand.createArguments.issueNumber, "5"); assert.equal(command.CreateCommand.createArguments.rewardUnit, "DEMO_CREDIT"); }); +test("encodes nested pull-request Int fields as strings for Daml values", () => { + const command = buildSubmitPullRequestCommand({ + packageId: "pkg123", + claimedBountyCid: "cid-claimed", + pullRequest: { + repository: "Saidur-droid/CommitLedger", + prNumber: 6, + prUrl: "https://github.com/Saidur-droid/CommitLedger/pull/6", + headSha: "abc", + baseBranch: "main" + } + }); + assert.equal(command.ExerciseCommand.choiceArgument.pullRequest.prNumber, "6"); +}); + test("builds the explicit revision path", () => { const command = buildReturnForRevisionCommand({ packageId: "pkg123", @@ -36,13 +53,14 @@ test("builds the explicit revision path", () => { assert.equal(command.ExerciseCommand.choiceArgument.reason, "Update tests"); }); -test("binds issue-aware canonical merge evidence into the verifier choice", () => { +test("binds issue-aware canonical merge evidence with Daml Int strings", () => { const evidence = { repository: "Saidur-droid/CommitLedger", issueNumber: 5, prNumber: 6, prUrl: "https://github.com/Saidur-droid/CommitLedger/pull/6", headSha: "abc", + mergeCommitSha: "b".repeat(40), baseBranch: "main", merged: true, mergedAt: "2026-09-27T10:57:30Z", @@ -54,7 +72,8 @@ test("binds issue-aware canonical merge evidence into the verifier choice", () = evidence }); assert.equal(command.ExerciseCommand.choice, "SubmittedBounty_VerifyMerged"); - assert.equal(command.ExerciseCommand.choiceArgument.evidence.issueNumber, 5); + assert.equal(command.ExerciseCommand.choiceArgument.evidence.issueNumber, "5"); + assert.equal(command.ExerciseCommand.choiceArgument.evidence.prNumber, "6"); }); test("settlement is an explicit consuming Daml choice", () => { From f5234d7f98e65eb26939f7728d8e7c207f68de96 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 22:54:49 +0600 Subject: [PATCH 049/144] fix: encode Daml Int values as JSON strings --- src/canton-commands.mjs | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/src/canton-commands.mjs b/src/canton-commands.mjs index c8f9b8c..b6463a6 100644 --- a/src/canton-commands.mjs +++ b/src/canton-commands.mjs @@ -4,6 +4,37 @@ function requireText(value, name) { return text; } +function damlInt(value, name) { + const number = Number(value); + if (!Number.isSafeInteger(number)) throw new Error(`${name} must be a safe integer`); + return String(number); +} + +function normalizePullRequestRef(pullRequest) { + return { + repository: requireText(pullRequest?.repository, "pullRequest.repository"), + prNumber: damlInt(pullRequest?.prNumber, "pullRequest.prNumber"), + prUrl: requireText(pullRequest?.prUrl, "pullRequest.prUrl"), + headSha: requireText(pullRequest?.headSha, "pullRequest.headSha"), + baseBranch: requireText(pullRequest?.baseBranch, "pullRequest.baseBranch") + }; +} + +function normalizeMergeEvidence(evidence) { + return { + repository: requireText(evidence?.repository, "evidence.repository"), + issueNumber: damlInt(evidence?.issueNumber, "evidence.issueNumber"), + prNumber: damlInt(evidence?.prNumber, "evidence.prNumber"), + prUrl: requireText(evidence?.prUrl, "evidence.prUrl"), + headSha: requireText(evidence?.headSha, "evidence.headSha"), + mergeCommitSha: requireText(evidence?.mergeCommitSha, "evidence.mergeCommitSha"), + baseBranch: requireText(evidence?.baseBranch, "evidence.baseBranch"), + merged: Boolean(evidence?.merged), + mergedAt: requireText(evidence?.mergedAt, "evidence.mergedAt"), + evidenceHash: requireText(evidence?.evidenceHash, "evidence.evidenceHash") + }; +} + export function templateId(packageId, template) { return `${requireText(packageId, "packageId")}:CommitLedger:${template}`; } @@ -29,7 +60,7 @@ export function buildCreateBountyCommand({ packageId, maintainer, verifier, boun verifier: requireText(verifier, "verifier"), bountyId: requireText(bounty.bountyId, "bountyId"), repository: requireText(bounty.repository, "repository"), - issueNumber: Number(bounty.issueNumber), + issueNumber: damlInt(bounty.issueNumber, "issueNumber"), issueUrl: requireText(bounty.issueUrl, "issueUrl"), title: requireText(bounty.title, "title"), rewardAmount: requireText(bounty.rewardAmount, "rewardAmount"), @@ -60,7 +91,7 @@ export function buildSubmitPullRequestCommand({ packageId, claimedBountyCid, pul templateId(packageId, "ClaimedBounty"), claimedBountyCid, "ClaimedBounty_SubmitPullRequest", - { pullRequest } + { pullRequest: normalizePullRequestRef(pullRequest) } ); } @@ -78,7 +109,7 @@ export function buildVerifyMergedCommand({ packageId, submittedBountyCid, eviden templateId(packageId, "SubmittedBounty"), submittedBountyCid, "SubmittedBounty_VerifyMerged", - { evidence } + { evidence: normalizeMergeEvidence(evidence) } ); } From 544b4c7b7da0c9f1b5451692411410f8a4e42f7b Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 23:07:49 +0600 Subject: [PATCH 050/144] build: add one-command proof reporter --- scripts/prove-and-report.sh | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 scripts/prove-and-report.sh diff --git a/scripts/prove-and-report.sh b/scripts/prove-and-report.sh new file mode 100644 index 0000000..6f5a58f --- /dev/null +++ b/scripts/prove-and-report.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -uo pipefail +cd "$(dirname "$0")/.." + +echo "=== COMMITLEDGER FULL PROOF ===" +echo "Commit: $(git rev-parse HEAD)" +echo + +bash scripts/run-local-proof.sh +status=$? + +echo +echo "=== EXIT STATUS: $status ===" + +if [ "$status" -eq 0 ]; then + echo "=== SUCCESS: VERIFIED EVIDENCE ===" + test -f evidence/verification.json && cat evidence/verification.json + echo + test -f evidence/canton-proof.json && cat evidence/canton-proof.json + echo + echo "Evidence directory:" + ls -lah evidence +else + echo "=== FAILURE DIAGNOSTICS ===" + for file in evidence/canton-demo.log evidence/canton-sandbox.log evidence/daml-tests.log evidence/daml-build.log evidence/dpm-version.log evidence/node-tests.log + do + if [ -f "$file" ]; then + echo + echo "--- $file ---" + tail -n 120 "$file" + fi + done +fi + +exit "$status" From c2f7578f806893368c5cc8e5034e4d55fa8c150e Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 23:07:51 +0600 Subject: [PATCH 051/144] build: add explicit verification stage markers --- scripts/verify-all.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/scripts/verify-all.sh b/scripts/verify-all.sh index 357e565..ca40b4b 100755 --- a/scripts/verify-all.sh +++ b/scripts/verify-all.sh @@ -6,13 +6,23 @@ rm -f evidence/verification.json export PATH="${DPM_HOME:-$HOME/.dpm}/bin:$PATH" export DPM_SDK_VERSION=3.5.12 SOURCE_COMMIT=$(git rev-parse HEAD) + +echo "=== STAGE 1/4: Node ===" node --version | tee evidence/node-version.log npm test 2>&1 | tee evidence/node-tests.log + +echo "=== STAGE 2/4: DPM ===" if ! command -v dpm >/dev/null 2>&1; then echo 'BLOCKED: DPM is not installed. Run bash scripts/bootstrap-dpm.sh on an internet-connected development machine.' | tee evidence/daml-build.log exit 2 fi (cd daml && dpm version --active) 2>&1 | tee evidence/dpm-version.log + +echo "=== STAGE 3/4: Daml build ===" (cd daml && dpm build) 2>&1 | tee evidence/daml-build.log + +echo "=== STAGE 4/4: Daml tests ===" (cd daml && dpm test) 2>&1 | tee evidence/daml-tests.log + SOURCE_COMMIT="$SOURCE_COMMIT" node -e 'require("node:fs").writeFileSync("evidence/verification.json",JSON.stringify({node:true,damlBuild:true,damlTests:true,verifiedAt:new Date().toISOString(),commit:process.env.SOURCE_COMMIT},null,2)+"\n")' +echo "=== BUILD/TEST VERIFICATION COMPLETE ===" From bf0cdaa4e9317ad9df27005dd16ef44a4fb492ca Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 23:07:54 +0600 Subject: [PATCH 052/144] test: lock one-command proof diagnostics --- tests/proof-wrapper-static.test.mjs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/proof-wrapper-static.test.mjs diff --git a/tests/proof-wrapper-static.test.mjs b/tests/proof-wrapper-static.test.mjs new file mode 100644 index 0000000..a68ced6 --- /dev/null +++ b/tests/proof-wrapper-static.test.mjs @@ -0,0 +1,20 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import fs from 'node:fs/promises'; + +const wrapper = await fs.readFile(new URL('../scripts/prove-and-report.sh', import.meta.url), 'utf8'); +const verify = await fs.readFile(new URL('../scripts/verify-all.sh', import.meta.url), 'utf8'); + +test('single proof wrapper always reports exit status and useful diagnostics', () => { + assert.match(wrapper, /EXIT STATUS/); + assert.match(wrapper, /canton-demo\.log/); + assert.match(wrapper, /canton-sandbox\.log/); + assert.match(wrapper, /daml-tests\.log/); + assert.match(wrapper, /node-tests\.log/); +}); + +test('verification prints explicit stage markers', () => { + for (const marker of ['STAGE 1\/4: Node','STAGE 2\/4: DPM','STAGE 3\/4: Daml build','STAGE 4\/4: Daml tests']) { + assert.match(verify, new RegExp(marker)); + } +}); From 539c0c0da3431f534e5f8306e3e0fb446c389aa9 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 23:22:04 +0600 Subject: [PATCH 053/144] fix: encode active contract offset as string --- src/canton-json-api.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/canton-json-api.mjs b/src/canton-json-api.mjs index 0925f8f..8ae344d 100644 --- a/src/canton-json-api.mjs +++ b/src/canton-json-api.mjs @@ -92,7 +92,7 @@ export class CantonJsonApi { return this.request("/v2/state/active-contracts", { method: "POST", body: JSON.stringify({ - activeAtOffset, + activeAtOffset: String(activeAtOffset), eventFormat: eventFormatForParty(party, templateId) }) }); From 2bb7be7c2f42309be8d3c8419e4569fcc792665b Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 23:22:20 +0600 Subject: [PATCH 054/144] test: require string active-contract offsets --- tests/security-regressions.test.mjs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/security-regressions.test.mjs b/tests/security-regressions.test.mjs index 3495f9d..2a970b2 100644 --- a/tests/security-regressions.test.mjs +++ b/tests/security-regressions.test.mjs @@ -116,3 +116,12 @@ test('ambiguous active contracts cannot silently choose an old demo receipt', as const api=new CantonJsonApi({baseUrl:'http://localhost:3975',token:'unit-token'}); await assert.rejects(api.findActiveContract({party:'unit-party',templateId:'pkg:CommitLedger:Bounty',activeAtOffset:1}), /Ambiguous/); }); + + +test('active-contract lookup encodes activeAtOffset as a JSON string', async t => { + let body; + t.mock.method(globalThis,'fetch',async (_url,options)=>{ body=JSON.parse(options.body); return Response.json([]); }); + const api=new CantonJsonApi({baseUrl:'http://localhost:3975',token:'unit-token'}); + await api.activeContracts({party:'unit-party',templateId:'pkg:CommitLedger:Bounty',activeAtOffset:42}); + assert.equal(body.activeAtOffset,'42'); +}); From cc6fc9168cbafbb8c7243df83c6f4d33dd7c2aac Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 23:22:34 +0600 Subject: [PATCH 055/144] build: trace verification failures --- scripts/verify-all.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/verify-all.sh b/scripts/verify-all.sh index ca40b4b..1c0c19d 100755 --- a/scripts/verify-all.sh +++ b/scripts/verify-all.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash -set -euo pipefail +set -eEuo pipefail +trap 'status=$?; echo "VERIFY ERROR: line $LINENO: $BASH_COMMAND (status $status)" >&2' ERR cd "$(dirname "$0")/.." mkdir -p evidence rm -f evidence/verification.json From 0cbb779370091f5e668fad75482129395d0865d2 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 23:22:43 +0600 Subject: [PATCH 056/144] build: trace proof-runner failures --- scripts/run-local-proof.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/run-local-proof.sh b/scripts/run-local-proof.sh index a324fef..95df943 100755 --- a/scripts/run-local-proof.sh +++ b/scripts/run-local-proof.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash -set -euo pipefail +set -eEuo pipefail +trap 'status=$?; echo "PROOF ERROR: line $LINENO: $BASH_COMMAND (status $status)" >&2' ERR cd "$(dirname "$0")/.." export PATH="${DPM_HOME:-$HOME/.dpm}/bin:$PATH" From b7d8b75874e4ad19b9e04f0031f0b58fa86e3c21 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 23:27:26 +0600 Subject: [PATCH 057/144] fix: atomically apply Canton v2 wire-format hardening From fcff04a042499bcc7d0e97a9e7b4ccb38af2e9dd Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 23:31:12 +0600 Subject: [PATCH 058/144] test: require explicit verification command statuses --- tests/proof-wrapper-static.test.mjs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/proof-wrapper-static.test.mjs b/tests/proof-wrapper-static.test.mjs index a68ced6..72be83d 100644 --- a/tests/proof-wrapper-static.test.mjs +++ b/tests/proof-wrapper-static.test.mjs @@ -18,3 +18,11 @@ test('verification prints explicit stage markers', () => { assert.match(verify, new RegExp(marker)); } }); + +test('verification avoids tee pipelines and checks command status explicitly', () => { + assert.doesNotMatch(verify, /npm test[^\n]*\|\s*tee/); + assert.doesNotMatch(verify, /dpm (?:version|build|test)[^\n]*\|\s*tee/); + assert.match(verify, /run_logged/); + assert.match(verify, /status=\$\?/); + assert.match(verify, /return "\$status"/); +}); From 5ec734cd89a341078a02e000b29c9310526c2367 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 23:31:27 +0600 Subject: [PATCH 059/144] fix: make verification stage progression explicit --- scripts/verify-all.sh | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/scripts/verify-all.sh b/scripts/verify-all.sh index 1c0c19d..06d7b3b 100755 --- a/scripts/verify-all.sh +++ b/scripts/verify-all.sh @@ -8,22 +8,34 @@ export PATH="${DPM_HOME:-$HOME/.dpm}/bin:$PATH" export DPM_SDK_VERSION=3.5.12 SOURCE_COMMIT=$(git rev-parse HEAD) +run_logged() { + local logfile=$1 + shift + set +e + "$@" >"$logfile" 2>&1 + status=$? + set -e + cat "$logfile" + return "$status" +} + echo "=== STAGE 1/4: Node ===" -node --version | tee evidence/node-version.log -npm test 2>&1 | tee evidence/node-tests.log +node --version > evidence/node-version.log 2>&1 +cat evidence/node-version.log +run_logged evidence/node-tests.log npm test echo "=== STAGE 2/4: DPM ===" if ! command -v dpm >/dev/null 2>&1; then echo 'BLOCKED: DPM is not installed. Run bash scripts/bootstrap-dpm.sh on an internet-connected development machine.' | tee evidence/daml-build.log exit 2 fi -(cd daml && dpm version --active) 2>&1 | tee evidence/dpm-version.log +run_logged evidence/dpm-version.log bash -lc 'cd daml && dpm version --active' echo "=== STAGE 3/4: Daml build ===" -(cd daml && dpm build) 2>&1 | tee evidence/daml-build.log +run_logged evidence/daml-build.log bash -lc 'cd daml && dpm build' echo "=== STAGE 4/4: Daml tests ===" -(cd daml && dpm test) 2>&1 | tee evidence/daml-tests.log +run_logged evidence/daml-tests.log bash -lc 'cd daml && dpm test' SOURCE_COMMIT="$SOURCE_COMMIT" node -e 'require("node:fs").writeFileSync("evidence/verification.json",JSON.stringify({node:true,damlBuild:true,damlTests:true,verifiedAt:new Date().toISOString(),commit:process.env.SOURCE_COMMIT},null,2)+"\n")' echo "=== BUILD/TEST VERIFICATION COMPLETE ===" From cf3d58d44eb6ec0c5ca141f1e15ac5d88ce7b761 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 23:31:38 +0600 Subject: [PATCH 060/144] fix: preserve DPM path in verification subshells --- scripts/verify-all.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/verify-all.sh b/scripts/verify-all.sh index 06d7b3b..0ae32d1 100755 --- a/scripts/verify-all.sh +++ b/scripts/verify-all.sh @@ -29,13 +29,13 @@ if ! command -v dpm >/dev/null 2>&1; then echo 'BLOCKED: DPM is not installed. Run bash scripts/bootstrap-dpm.sh on an internet-connected development machine.' | tee evidence/daml-build.log exit 2 fi -run_logged evidence/dpm-version.log bash -lc 'cd daml && dpm version --active' +run_logged evidence/dpm-version.log bash -c 'cd daml && dpm version --active' echo "=== STAGE 3/4: Daml build ===" -run_logged evidence/daml-build.log bash -lc 'cd daml && dpm build' +run_logged evidence/daml-build.log bash -c 'cd daml && dpm build' echo "=== STAGE 4/4: Daml tests ===" -run_logged evidence/daml-tests.log bash -lc 'cd daml && dpm test' +run_logged evidence/daml-tests.log bash -c 'cd daml && dpm test' SOURCE_COMMIT="$SOURCE_COMMIT" node -e 'require("node:fs").writeFileSync("evidence/verification.json",JSON.stringify({node:true,damlBuild:true,damlTests:true,verifiedAt:new Date().toISOString(),commit:process.env.SOURCE_COMMIT},null,2)+"\n")' echo "=== BUILD/TEST VERIFICATION COMPLETE ===" From 4d8f38515d9e5290070ef36ca20e19949e3de15a Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 23:36:57 +0600 Subject: [PATCH 061/144] test: lock explicit proof status handling --- tests/proof-wrapper-static.test.mjs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/proof-wrapper-static.test.mjs b/tests/proof-wrapper-static.test.mjs index 72be83d..4070bec 100644 --- a/tests/proof-wrapper-static.test.mjs +++ b/tests/proof-wrapper-static.test.mjs @@ -26,3 +26,10 @@ test('verification avoids tee pipelines and checks command status explicitly', ( assert.match(verify, /status=\$\?/); assert.match(verify, /return "\$status"/); }); + + +test('proof shell scripts use explicit status control', () => { + assert.ok(wrapper.includes('set +e')); + assert.ok(verify.includes('if "$@" >"$logfile" 2>&1; then')); + assert.equal(verify.includes('set +e'), false); +}); From 34af679af8b9500555f23727822d8c3a74d1845b Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 23:37:17 +0600 Subject: [PATCH 062/144] fix: make proof wrapper immune to inherited errexit --- scripts/prove-and-report.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/prove-and-report.sh b/scripts/prove-and-report.sh index 6f5a58f..27ad260 100644 --- a/scripts/prove-and-report.sh +++ b/scripts/prove-and-report.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +set +e set -uo pipefail cd "$(dirname "$0")/.." From 75ae2eec34344d2c76f918672bef590b4920aa9a Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 23:37:39 +0600 Subject: [PATCH 063/144] fix: make proof wrapper immune to inherited errexit --- scripts/prove-and-report.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/prove-and-report.sh b/scripts/prove-and-report.sh index 27ad260..d2eda26 100644 --- a/scripts/prove-and-report.sh +++ b/scripts/prove-and-report.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash set +e +set +e set -uo pipefail cd "$(dirname "$0")/.." From 6d20c068e9ef441261dc293320617ec0fc852398 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 23:37:55 +0600 Subject: [PATCH 064/144] fix: use explicit verification command status flow --- scripts/verify-all.sh | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/scripts/verify-all.sh b/scripts/verify-all.sh index 0ae32d1..69c0223 100755 --- a/scripts/verify-all.sh +++ b/scripts/verify-all.sh @@ -11,12 +11,14 @@ SOURCE_COMMIT=$(git rev-parse HEAD) run_logged() { local logfile=$1 shift - set +e - "$@" >"$logfile" 2>&1 - status=$? - set -e - cat "$logfile" - return "$status" + if "$@" >"$logfile" 2>&1; then + cat "$logfile" + return 0 + else + status=$? + cat "$logfile" + return "$status" + fi } echo "=== STAGE 1/4: Node ===" @@ -26,7 +28,8 @@ run_logged evidence/node-tests.log npm test echo "=== STAGE 2/4: DPM ===" if ! command -v dpm >/dev/null 2>&1; then - echo 'BLOCKED: DPM is not installed. Run bash scripts/bootstrap-dpm.sh on an internet-connected development machine.' | tee evidence/daml-build.log + echo 'BLOCKED: DPM is not installed. Run bash scripts/bootstrap-dpm.sh on an internet-connected development machine.' > evidence/daml-build.log + cat evidence/daml-build.log exit 2 fi run_logged evidence/dpm-version.log bash -c 'cd daml && dpm version --active' From f7fc27da93a2ea551cbbeb8288e86c1a1a14f90b Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 23:42:10 +0600 Subject: [PATCH 065/144] test: require explicit stage status runner --- tests/proof-wrapper-static.test.mjs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/tests/proof-wrapper-static.test.mjs b/tests/proof-wrapper-static.test.mjs index 4070bec..981afb7 100644 --- a/tests/proof-wrapper-static.test.mjs +++ b/tests/proof-wrapper-static.test.mjs @@ -19,17 +19,9 @@ test('verification prints explicit stage markers', () => { } }); -test('verification avoids tee pipelines and checks command status explicitly', () => { - assert.doesNotMatch(verify, /npm test[^\n]*\|\s*tee/); - assert.doesNotMatch(verify, /dpm (?:version|build|test)[^\n]*\|\s*tee/); - assert.match(verify, /run_logged/); - assert.match(verify, /status=\$\?/); - assert.match(verify, /return "\$status"/); -}); - - -test('proof shell scripts use explicit status control', () => { - assert.ok(wrapper.includes('set +e')); - assert.ok(verify.includes('if "$@" >"$logfile" 2>&1; then')); - assert.equal(verify.includes('set +e'), false); +test('verification uses explicit stage status handling instead of shell errexit', () => { + assert.doesNotMatch(verify, /set -[^\n]*e/); + assert.match(verify, /run_stage/); + assert.match(verify, /if "\$@" >"\$logfile" 2>&1; then/); + assert.match(verify, /FAILED with status/); }); From ce1fa8791557cfa36bf11685ecacfac0819ba989 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 23:42:31 +0600 Subject: [PATCH 066/144] fix: make verification stages explicit and shell-agnostic --- scripts/verify-all.sh | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/scripts/verify-all.sh b/scripts/verify-all.sh index 69c0223..eb90f94 100755 --- a/scripts/verify-all.sh +++ b/scripts/verify-all.sh @@ -1,6 +1,5 @@ #!/usr/bin/env bash -set -eEuo pipefail -trap 'status=$?; echo "VERIFY ERROR: line $LINENO: $BASH_COMMAND (status $status)" >&2' ERR +set -uo pipefail cd "$(dirname "$0")/.." mkdir -p evidence rm -f evidence/verification.json @@ -8,23 +7,24 @@ export PATH="${DPM_HOME:-$HOME/.dpm}/bin:$PATH" export DPM_SDK_VERSION=3.5.12 SOURCE_COMMIT=$(git rev-parse HEAD) -run_logged() { - local logfile=$1 - shift +run_stage() { + local label=$1 + local logfile=$2 + shift 2 + echo "=== $label ===" if "$@" >"$logfile" 2>&1; then cat "$logfile" + echo "=== $label: PASS ===" return 0 else status=$? cat "$logfile" + echo "=== $label: FAILED with status $status ===" >&2 return "$status" fi } -echo "=== STAGE 1/4: Node ===" -node --version > evidence/node-version.log 2>&1 -cat evidence/node-version.log -run_logged evidence/node-tests.log npm test +run_stage "STAGE 1/4: Node" evidence/node-tests.log npm test || exit $? echo "=== STAGE 2/4: DPM ===" if ! command -v dpm >/dev/null 2>&1; then @@ -32,13 +32,9 @@ if ! command -v dpm >/dev/null 2>&1; then cat evidence/daml-build.log exit 2 fi -run_logged evidence/dpm-version.log bash -c 'cd daml && dpm version --active' - -echo "=== STAGE 3/4: Daml build ===" -run_logged evidence/daml-build.log bash -c 'cd daml && dpm build' - -echo "=== STAGE 4/4: Daml tests ===" -run_logged evidence/daml-tests.log bash -c 'cd daml && dpm test' +run_stage "STAGE 2/4: DPM" evidence/dpm-version.log bash -c 'cd daml && dpm version --active' || exit $? +run_stage "STAGE 3/4: Daml build" evidence/daml-build.log bash -c 'cd daml && dpm build' || exit $? +run_stage "STAGE 4/4: Daml tests" evidence/daml-tests.log bash -c 'cd daml && dpm test' || exit $? SOURCE_COMMIT="$SOURCE_COMMIT" node -e 'require("node:fs").writeFileSync("evidence/verification.json",JSON.stringify({node:true,damlBuild:true,damlTests:true,verifiedAt:new Date().toISOString(),commit:process.env.SOURCE_COMMIT},null,2)+"\n")' echo "=== BUILD/TEST VERIFICATION COMPLETE ===" From bebc4195eee835a2f41fa2fdf83408d3c37feacc Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Tue, 29 Sep 2026 23:42:48 +0600 Subject: [PATCH 067/144] chore: normalize proof wrapper shell options --- scripts/prove-and-report.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/prove-and-report.sh b/scripts/prove-and-report.sh index d2eda26..27ad260 100644 --- a/scripts/prove-and-report.sh +++ b/scripts/prove-and-report.sh @@ -1,6 +1,5 @@ #!/usr/bin/env bash set +e -set +e set -uo pipefail cd "$(dirname "$0")/.." From 7a2d9711299272fa3d731777881489e9aa17ef18 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 08:46:46 +0600 Subject: [PATCH 068/144] docs: refresh verified delivery status for final candidate --- docs/DELIVERY_STATUS.md | 94 +++++++++++++++++++++++++---------------- 1 file changed, 57 insertions(+), 37 deletions(-) diff --git a/docs/DELIVERY_STATUS.md b/docs/DELIVERY_STATUS.md index c52273e..4b3904f 100644 --- a/docs/DELIVERY_STATUS.md +++ b/docs/DELIVERY_STATUS.md @@ -1,64 +1,84 @@ -# Delivery status — 29 September 2026 +# Delivery status — 30 September 2026 Delivery branch: `fix/verified-delivery-20260929`. +## Current candidate + +- Candidate head after synchronizing `main`: `51e4ddd95a442fd11a8df80a867361db9b86c080`. +- Branch relation: **68 commits ahead, 0 behind** `main`. +- PR #8 remains **draft** and mergeable. +- The synchronization incorporated `AGENTS.md` and `docs/PROJECT_MODE.md` from `main` with a real two-parent merge commit; no force push was used. + ## Verified evidence -- Original Node baseline: **16/16 passed** on the earlier delivery baseline. -- Expanded Node suite: **65/65 passed** on delivery commit `202693cbc10d835166fe3bf6240fa56e529ff1cd`. -- Competition-readiness evaluator was developed test-first in an isolated Node fixture: **4/4 passed** before pushing the same source/tests to this branch. -- Judge-UI static acceptance tests were developed test-first in an isolated fixture: **3/3 passed** before pushing the same markup/tests to this branch. -- Official HackCanton Season 3 Rules, Timeline, Tracks and Materials were rechecked on 2026-09-29. The binding competition design is `docs/superpowers/specs/2026-09-29-hackcanton-s3-competition-ready-design.md`. +- Original Node baseline: **16/16 passed**. +- Expanded delivery suite: **65/65 passed** on commit `202693cbc10d835166fe3bf6240fa56e529ff1cd`. +- A later Codespaces execution reported **74/74 Node tests passed**, DPM 3.5.12 executed, and the Daml DAR build succeeded before the final runtime hardening sweep. +- Competition-readiness evaluator: **4/4 passed** in its isolated test-first fixture. +- Judge-UI static acceptance tests: **3/3 passed** in their isolated test-first fixture. +- Official HackCanton Season 3 Rules, Timeline, Tracks and Materials were rechecked on 2026-09-29. -These results do **not** establish that the full Node suite is green on the newest branch head. The latest head still needs a clean full-suite run. +These historical results are strong implementation evidence but do **not** prove the current candidate head. Final proof must be generated from one run against the exact candidate source commit. ## Implemented ### Verification and security -- Repository-qualified GitHub issue references, canonical PR/merge-commit verification and target-branch reachability. -- Merge commit SHA propagated through merge evidence and SettlementReceipt. -- Structured Canton rejection handling: network, auth, timeout, throttling and unknown infrastructure errors cannot become security proof. +- Repository-qualified issue references, canonical PR/merge-commit verification and target-branch reachability. +- Merge commit SHA propagated through MergeEvidence and SettlementReceipt. +- Structured Canton rejection classification: network, authentication, timeout, throttling and unknown infrastructure errors cannot be promoted into security proof. - Unique demo identities, unambiguous active-contract lookup and GitHub preflight before ledger writes. - Server-side origin/content-type protection and no arbitrary browser command submission using server-held ledger credentials. +- Evidence provenance is bound to source commit and run identity; mixed evidence is rejected. ### Competition readiness -- Fail-closed `PASS / BLOCKED / FAIL` competition-readiness engine. +- Fail-closed `PASS / BLOCKED / FAIL` readiness engine. - External/account/publication gates remain BLOCKED until actually verified. -- Verified implementation evidence from different run IDs or source commits is rejected as incoherent. -- Track 1 business brief, pilot plan, AI disclosure and hackathon-period work disclosure. -- Final checklist and submission draft aligned to the live Season 3 rules. +- Track 1 business brief, pilot plan, pitch, AI disclosure and hackathon-period work disclosure are present. +- Submission draft, judge runbook and final checklist are aligned to the competition design. ### Judge experience -- Web UI now exposes Track 1, competition-readiness state and all six official judging criteria. -- Runtime-sensitive states do not display fake transaction IDs or simulated success. -- Figma judge-flow file includes desktop and mobile evidence-first layouts. -- Linear project tracks Competition Compliance, Runtime Proof, Judge UX and Submission Package. +- Judge UI exposes Track 1 positioning, competition readiness and all six judging criteria. +- Runtime-sensitive states never display invented Canton transaction IDs or simulated success as real proof. +- Desktop/mobile judge-flow design and <=5-minute evidence-led demo script are prepared. ## Current blockers -### Runtime proof -- The connected Remote Desktop development computer is currently **offline**. Earlier inspection showed Git/Node/npm/WSL available, but DPM/Daml were not installed. -- Therefore DPM 3.5.12 installation, current Daml build/test, DAR deployment, real Canton lifecycle, SettlementReceipt and exact negative ledger rejection evidence have not been produced on the newest head. -- Full browser/mobile QA against the running app and the final ≤5-minute evidence-backed video remain pending. +### Real Canton proof +The only connected development device, `DESKTOP-KTDVO5M`, is currently **offline**. Because there is no active execution machine, the following cannot truthfully be produced in this session: + +- clean full-suite execution on the exact current candidate; +- DPM/Daml build and Daml Script test logs on the exact current candidate; +- DAR deployment to a live local Canton runtime; +- real Issue -> Bounty -> Claim -> PR -> Verify -> Settle -> Receipt lifecycle; +- exact wrong-evidence, unauthorized-settlement and duplicate/replay rejections; +- coherent final evidence bundle from the same run and source commit; +- browser/mobile QA against the running app; +- final evidence-backed video. + +When an execution machine is available, the intended operator command is: + +```bash +bash scripts/prove-and-report.sh +``` + +A successful run must be retained under `evidence/` and validated before this status changes to ready. ### GitHub Actions -Latest workflow for head `3c420168453ac68f9dd1bde57e63106e29aaaeb9`: -- run `36580546442` -- jobs: `node`, `canton-proof`, `repository-gates` -- all completed with failure **before any recorded step ran** -- each job reports `steps: []`, `runner_id: 0`, and an empty runner name +The latest observed GitHub Actions runs failed before recorded job steps began (`steps: []`). That is not evidence of a test assertion failure. The exact platform/account-level cause is not exposed by the current connector, so no billing, policy, runner or code cause is asserted without evidence. -That result is not evidence of a test assertion failure. The exact GitHub platform/account-level cause is not exposed by the available connection, so do not label it billing, policy or code failure without evidence. +A new push has been created on 2026-09-30 after syncing `main`; CI for the new candidate must be evaluated when GitHub exposes the run. -### External competition gates intentionally deferred by owner -- make repository/public judging materials accessible; -- verify AppsFactory 1,000 Mana; -- verify minimum 10 days platform activity; -- verify journal/project profile/account eligibility; -- final portal submission and receipt. +### External competition gates +Still intentionally unverified: +- public repository/materials accessible to a logged-out judge; +- AppsFactory 1,000 Mana; +- minimum platform activity requirement; +- project profile and journal; +- team/account eligibility; +- final portal submission and retained receipt. -## Current release status +## Release decision -The architecture, competition specification, business package, readiness automation, judge-flow design and web scoring UI are advanced, but CommitLedger is **not yet runtime-verified or submission-ready**. +CommitLedger is **implementation-advanced but not yet runtime-verified or submission-ready**. -Keep PR #8 draft. Do not claim 100%, close the live-Canton evidence gate, merge based on source inspection alone, or submit until the real runtime and external gates are satisfied. +Keep PR #8 draft. Do not merge, close issue #4, claim 100%, or submit until the exact candidate produces real Canton proof and the external competition gates are verified. From 38f41bfbfac7203a2f5db5ed1b55bb8afc3cae2f Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 08:59:22 +0600 Subject: [PATCH 069/144] test: distinguish errexit from pipefail options --- tests/proof-wrapper-static.test.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/proof-wrapper-static.test.mjs b/tests/proof-wrapper-static.test.mjs index 981afb7..d5b5b2d 100644 --- a/tests/proof-wrapper-static.test.mjs +++ b/tests/proof-wrapper-static.test.mjs @@ -14,13 +14,13 @@ test('single proof wrapper always reports exit status and useful diagnostics', ( }); test('verification prints explicit stage markers', () => { - for (const marker of ['STAGE 1\/4: Node','STAGE 2\/4: DPM','STAGE 3\/4: Daml build','STAGE 4\/4: Daml tests']) { + for (const marker of ['STAGE 1\\/4: Node','STAGE 2\\/4: DPM','STAGE 3\\/4: Daml build','STAGE 4\\/4: Daml tests']) { assert.match(verify, new RegExp(marker)); } }); test('verification uses explicit stage status handling instead of shell errexit', () => { - assert.doesNotMatch(verify, /set -[^\n]*e/); + assert.doesNotMatch(verify, /^set\s+-[A-Za-z]*e[A-Za-z]*\s*(?:#.*)?$/m); assert.match(verify, /run_stage/); assert.match(verify, /if "\$@" >"\$logfile" 2>&1; then/); assert.match(verify, /FAILED with status/); From 87cd3e1a0db3e076fe4df423a6687f05f6a01fdc Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 08:59:48 +0600 Subject: [PATCH 070/144] fix: send Canton active-contract offset as int64 --- src/canton-json-api.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/canton-json-api.mjs b/src/canton-json-api.mjs index 8ae344d..085ff3d 100644 --- a/src/canton-json-api.mjs +++ b/src/canton-json-api.mjs @@ -92,7 +92,7 @@ export class CantonJsonApi { return this.request("/v2/state/active-contracts", { method: "POST", body: JSON.stringify({ - activeAtOffset: String(activeAtOffset), + activeAtOffset: Number(activeAtOffset), eventFormat: eventFormatForParty(party, templateId) }) }); From 5a99500655e984c1451d57b131d69f69744298e9 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 08:59:51 +0600 Subject: [PATCH 071/144] test: match Canton v2 activeAtOffset wire type --- tests/security-regressions.test.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/security-regressions.test.mjs b/tests/security-regressions.test.mjs index 2a970b2..57186bb 100644 --- a/tests/security-regressions.test.mjs +++ b/tests/security-regressions.test.mjs @@ -118,10 +118,10 @@ test('ambiguous active contracts cannot silently choose an old demo receipt', as }); -test('active-contract lookup encodes activeAtOffset as a JSON string', async t => { +test('active-contract lookup encodes activeAtOffset as a JSON int64', async t => { let body; t.mock.method(globalThis,'fetch',async (_url,options)=>{ body=JSON.parse(options.body); return Response.json([]); }); const api=new CantonJsonApi({baseUrl:'http://localhost:3975',token:'unit-token'}); await api.activeContracts({party:'unit-party',templateId:'pkg:CommitLedger:Bounty',activeAtOffset:42}); - assert.equal(body.activeAtOffset,'42'); + assert.equal(body.activeAtOffset,42); }); From 91ba0da07f14942cf49dbdbf132cdc60a00a45d0 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 09:00:14 +0600 Subject: [PATCH 072/144] fix: normalize nested Daml numeric values at JSON boundary --- src/canton-json-api.mjs | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/canton-json-api.mjs b/src/canton-json-api.mjs index 085ff3d..57d38c4 100644 --- a/src/canton-json-api.mjs +++ b/src/canton-json-api.mjs @@ -6,6 +6,38 @@ function requireText(value, name) { return text; } +function encodeDamlValue(value) { + if (typeof value === "number") { + if (!Number.isFinite(value)) throw new Error("Daml numeric values must be finite"); + return String(value); + } + if (Array.isArray(value)) return value.map(encodeDamlValue); + if (value && typeof value === "object") { + return Object.fromEntries(Object.entries(value).map(([key, nested]) => [key, encodeDamlValue(nested)])); + } + return value; +} + +function encodeCommandDamlValues(command) { + if (command?.CreateCommand) { + return { + CreateCommand: { + ...command.CreateCommand, + createArguments: encodeDamlValue(command.CreateCommand.createArguments) + } + }; + } + if (command?.ExerciseCommand) { + return { + ExerciseCommand: { + ...command.ExerciseCommand, + choiceArgument: encodeDamlValue(command.ExerciseCommand.choiceArgument) + } + }; + } + return command; +} + function authHeaders(token) { return { ...(token ? { Authorization: `Bearer ${token}` } : {}), @@ -73,7 +105,7 @@ export class CantonJsonApi { return this.request("/v2/commands/submit-and-wait", { method: "POST", body: JSON.stringify({ - commands, + commands: commands.map(encodeCommandDamlValues), workflowId, userId: this.userId, commandId, From 8533dc343ea01abe290325d7073075c6550d3953 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 09:00:23 +0600 Subject: [PATCH 073/144] test: lock Daml numeric wire encoding regression --- tests/security-regressions.test.mjs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/security-regressions.test.mjs b/tests/security-regressions.test.mjs index 57186bb..602f5b9 100644 --- a/tests/security-regressions.test.mjs +++ b/tests/security-regressions.test.mjs @@ -125,3 +125,19 @@ test('active-contract lookup encodes activeAtOffset as a JSON int64', async t => await api.activeContracts({party:'unit-party',templateId:'pkg:CommitLedger:Bounty',activeAtOffset:42}); assert.equal(body.activeAtOffset,42); }); + +test('submit-and-wait normalizes nested Daml numeric values to JSON strings', async t => { + let body; + t.mock.method(globalThis, 'fetch', async (_url, options) => { body = JSON.parse(options.body); return Response.json({}); }); + const api = new CantonJsonApi({baseUrl:'http://localhost:3975',token:'unit-token'}); + await api.submitAndWait({ + commands:[{CreateCommand:{templateId:'pkg:CommitLedger:Bounty',createArguments:{issueNumber:5,rewardAmount:100.0,nested:{prNumber:6},items:[7]}}}], + actAs:['Maintainer::1'], + commandId:'unit-numeric-normalization' + }); + const args = body.commands[0].CreateCommand.createArguments; + assert.equal(args.issueNumber, '5'); + assert.equal(args.rewardAmount, '100'); + assert.equal(args.nested.prNumber, '6'); + assert.deepEqual(args.items, ['7']); +}); From 1db7c667becdb7fe300e1b473d8c1c340e7fe9ae Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 09:03:09 +0600 Subject: [PATCH 074/144] fix: make verification compatible with strict no-errexit guard --- scripts/verify-all.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/verify-all.sh b/scripts/verify-all.sh index eb90f94..66f3582 100755 --- a/scripts/verify-all.sh +++ b/scripts/verify-all.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -set -uo pipefail +set -u cd "$(dirname "$0")/.." mkdir -p evidence rm -f evidence/verification.json From 7163d05c2ba06d6413d9d6497a9e680ab72e9c54 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:01:13 +0600 Subject: [PATCH 075/144] fix: make verification stage progression deterministic --- scripts/verify-all.sh | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/scripts/verify-all.sh b/scripts/verify-all.sh index 66f3582..9c0a633 100755 --- a/scripts/verify-all.sh +++ b/scripts/verify-all.sh @@ -7,24 +7,25 @@ export PATH="${DPM_HOME:-$HOME/.dpm}/bin:$PATH" export DPM_SDK_VERSION=3.5.12 SOURCE_COMMIT=$(git rev-parse HEAD) -run_stage() { +run_logged() { local label=$1 local logfile=$2 shift 2 echo "=== $label ===" - if "$@" >"$logfile" 2>&1; then - cat "$logfile" - echo "=== $label: PASS ===" - return 0 - else - status=$? - cat "$logfile" + "$@" >"$logfile" 2>&1 + local status=$? + cat "$logfile" + if [ "$status" -ne 0 ]; then echo "=== $label: FAILED with status $status ===" >&2 return "$status" fi + echo "=== $label: PASS ===" + return 0 } -run_stage "STAGE 1/4: Node" evidence/node-tests.log npm test || exit $? +run_logged "STAGE 1/4: Node" evidence/node-tests.log npm test +status=$? +if [ "$status" -ne 0 ]; then exit "$status"; fi echo "=== STAGE 2/4: DPM ===" if ! command -v dpm >/dev/null 2>&1; then @@ -32,9 +33,17 @@ if ! command -v dpm >/dev/null 2>&1; then cat evidence/daml-build.log exit 2 fi -run_stage "STAGE 2/4: DPM" evidence/dpm-version.log bash -c 'cd daml && dpm version --active' || exit $? -run_stage "STAGE 3/4: Daml build" evidence/daml-build.log bash -c 'cd daml && dpm build' || exit $? -run_stage "STAGE 4/4: Daml tests" evidence/daml-tests.log bash -c 'cd daml && dpm test' || exit $? +run_logged "STAGE 2/4: DPM" evidence/dpm-version.log bash -c 'cd daml && dpm version --active' +status=$? +if [ "$status" -ne 0 ]; then exit "$status"; fi + +run_logged "STAGE 3/4: Daml build" evidence/daml-build.log bash -c 'cd daml && dpm build' +status=$? +if [ "$status" -ne 0 ]; then exit "$status"; fi + +run_logged "STAGE 4/4: Daml tests" evidence/daml-tests.log bash -c 'cd daml && dpm test' +status=$? +if [ "$status" -ne 0 ]; then exit "$status"; fi SOURCE_COMMIT="$SOURCE_COMMIT" node -e 'require("node:fs").writeFileSync("evidence/verification.json",JSON.stringify({node:true,damlBuild:true,damlTests:true,verifiedAt:new Date().toISOString(),commit:process.env.SOURCE_COMMIT},null,2)+"\n")' echo "=== BUILD/TEST VERIFICATION COMPLETE ===" From 1cca521d2b979bf111551b083e7bdcca3e7c3a62 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:01:30 +0600 Subject: [PATCH 076/144] test: verify proof stages by behavior not helper name --- tests/proof-wrapper-static.test.mjs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/proof-wrapper-static.test.mjs b/tests/proof-wrapper-static.test.mjs index d5b5b2d..68a2724 100644 --- a/tests/proof-wrapper-static.test.mjs +++ b/tests/proof-wrapper-static.test.mjs @@ -19,9 +19,11 @@ test('verification prints explicit stage markers', () => { } }); -test('verification uses explicit stage status handling instead of shell errexit', () => { +test('verification advances stages with explicit status checks and no shell errexit', () => { assert.doesNotMatch(verify, /^set\s+-[A-Za-z]*e[A-Za-z]*\s*(?:#.*)?$/m); - assert.match(verify, /run_stage/); - assert.match(verify, /if "\$@" >"\$logfile" 2>&1; then/); + assert.match(verify, /run_logged/); + assert.match(verify, /local status=\$\?/); + assert.match(verify, /if \[ "\$status" -ne 0 \]; then/); assert.match(verify, /FAILED with status/); + assert.match(verify, /BUILD\/TEST VERIFICATION COMPLETE/); }); From 5a719636d358c51d1db2a28e62696ed70826fdef Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:01:44 +0600 Subject: [PATCH 077/144] fix: block stale checkout proof runs --- scripts/run-local-proof.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/scripts/run-local-proof.sh b/scripts/run-local-proof.sh index 95df943..ffe3583 100755 --- a/scripts/run-local-proof.sh +++ b/scripts/run-local-proof.sh @@ -11,6 +11,17 @@ rm -f evidence/canton-proof.json evidence/ledger-setup.json evidence/canton-port SOURCE_COMMIT=$(git rev-parse HEAD) export GITHUB_SHA="$SOURCE_COMMIT" +CURRENT_BRANCH=$(git branch --show-current) +if [ -n "$CURRENT_BRANCH" ]; then + git fetch --quiet origin "$CURRENT_BRANCH" || true + REMOTE_COMMIT=$(git rev-parse "origin/$CURRENT_BRANCH" 2>/dev/null || true) + if [ -n "$REMOTE_COMMIT" ] && [ "$REMOTE_COMMIT" != "$SOURCE_COMMIT" ]; then + echo "BLOCKED: stale checkout. Local $SOURCE_COMMIT, origin/$CURRENT_BRANCH $REMOTE_COMMIT." >&2 + echo "Run: git reset --hard origin/$CURRENT_BRANCH" >&2 + exit 3 + fi +fi + if ! git diff --quiet || ! git diff --cached --quiet; then echo 'Tracked working-tree changes detected. Commit or stash them before generating competition evidence.' >&2 exit 2 From 994928360e09754a1c8e4c2315988c19b086273e Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:09:48 +0600 Subject: [PATCH 078/144] fix: use Canton 3.5 package-name template identifiers --- src/canton-commands.mjs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/canton-commands.mjs b/src/canton-commands.mjs index b6463a6..596da2a 100644 --- a/src/canton-commands.mjs +++ b/src/canton-commands.mjs @@ -35,8 +35,8 @@ function normalizeMergeEvidence(evidence) { }; } -export function templateId(packageId, template) { - return `${requireText(packageId, "packageId")}:CommitLedger:${template}`; +export function templateId(packageName, template) { + return `#${requireText(packageName, "packageName")}:CommitLedger:${template}`; } export function createCommand(template, args) { @@ -54,8 +54,8 @@ export function exerciseCommand(template, contractId, choice, choiceArgument = { }; } -export function buildCreateBountyCommand({ packageId, maintainer, verifier, bounty }) { - return createCommand(templateId(packageId, "Bounty"), { +export function buildCreateBountyCommand({ packageName, maintainer, verifier, bounty }) { + return createCommand(templateId(packageName, "Bounty"), { maintainer: requireText(maintainer, "maintainer"), verifier: requireText(verifier, "verifier"), bountyId: requireText(bounty.bountyId, "bountyId"), @@ -68,8 +68,8 @@ export function buildCreateBountyCommand({ packageId, maintainer, verifier, boun }); } -export function buildClaimRequestCommand({ packageId, contributor, maintainer, bountyId, contributorGithub }) { - return createCommand(templateId(packageId, "ClaimRequest"), { +export function buildClaimRequestCommand({ packageName, contributor, maintainer, bountyId, contributorGithub }) { + return createCommand(templateId(packageName, "ClaimRequest"), { contributor: requireText(contributor, "contributor"), maintainer: requireText(maintainer, "maintainer"), bountyId: requireText(bountyId, "bountyId"), @@ -77,45 +77,45 @@ export function buildClaimRequestCommand({ packageId, contributor, maintainer, b }); } -export function buildAcceptClaimCommand({ packageId, claimRequestCid, bountyCid }) { +export function buildAcceptClaimCommand({ packageName, claimRequestCid, bountyCid }) { return exerciseCommand( - templateId(packageId, "ClaimRequest"), + templateId(packageName, "ClaimRequest"), claimRequestCid, "ClaimRequest_Accept", { bountyCid: requireText(bountyCid, "bountyCid") } ); } -export function buildSubmitPullRequestCommand({ packageId, claimedBountyCid, pullRequest }) { +export function buildSubmitPullRequestCommand({ packageName, claimedBountyCid, pullRequest }) { return exerciseCommand( - templateId(packageId, "ClaimedBounty"), + templateId(packageName, "ClaimedBounty"), claimedBountyCid, "ClaimedBounty_SubmitPullRequest", { pullRequest: normalizePullRequestRef(pullRequest) } ); } -export function buildReturnForRevisionCommand({ packageId, submittedBountyCid, reason }) { +export function buildReturnForRevisionCommand({ packageName, submittedBountyCid, reason }) { return exerciseCommand( - templateId(packageId, "SubmittedBounty"), + templateId(packageName, "SubmittedBounty"), submittedBountyCid, "SubmittedBounty_ReturnForRevision", { reason: requireText(reason, "reason") } ); } -export function buildVerifyMergedCommand({ packageId, submittedBountyCid, evidence }) { +export function buildVerifyMergedCommand({ packageName, submittedBountyCid, evidence }) { return exerciseCommand( - templateId(packageId, "SubmittedBounty"), + templateId(packageName, "SubmittedBounty"), submittedBountyCid, "SubmittedBounty_VerifyMerged", { evidence: normalizeMergeEvidence(evidence) } ); } -export function buildSettleCommand({ packageId, verifiedBountyCid, settledAt, settlementRef }) { +export function buildSettleCommand({ packageName, verifiedBountyCid, settledAt, settlementRef }) { return exerciseCommand( - templateId(packageId, "VerifiedBounty"), + templateId(packageName, "VerifiedBounty"), verifiedBountyCid, "VerifiedBounty_Settle", { From 42a70bb26fae98c440ad30b6180cb4a749799ca1 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:10:04 +0600 Subject: [PATCH 079/144] fix: pin package selection while matching package-name identifiers --- src/canton-json-api.mjs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/canton-json-api.mjs b/src/canton-json-api.mjs index 57d38c4..1a929fd 100644 --- a/src/canton-json-api.mjs +++ b/src/canton-json-api.mjs @@ -101,7 +101,7 @@ export class CantonJsonApi { return body; } - async submitAndWait({ commands, actAs, readAs = [], workflowId, commandId }) { + async submitAndWait({ commands, actAs, readAs = [], workflowId, commandId, packageIdSelectionPreference = [] }) { return this.request("/v2/commands/submit-and-wait", { method: "POST", body: JSON.stringify({ @@ -115,7 +115,7 @@ export class CantonJsonApi { submissionId: commandId, disclosedContracts: [], synchronizerId: "", - packageIdSelectionPreference: [] + packageIdSelectionPreference }) }); } @@ -133,7 +133,11 @@ export class CantonJsonApi { async findActiveContract({ party, templateId, activeAtOffset, predicate = () => true }) { const raw = await this.activeContracts({ party, templateId, activeAtOffset }); const events = extractCreatedEvents(raw); - const matches = events.filter(candidate => candidate.templateId === templateId && predicate(candidate?.createArgument || {})); + const requestedEntity = String(templateId).split(':').slice(-2).join(':'); + const matches = events.filter(candidate => { + const candidateEntity = String(candidate?.templateId || '').split(':').slice(-2).join(':'); + return candidateEntity === requestedEntity && predicate(candidate?.createArgument || {}); + }); if (matches.length > 1) throw new Error(`Ambiguous active contracts for template ${templateId}`); const event = matches[0]; if (!event?.contractId) throw new Error(`Active contract not found for template ${templateId} at offset ${activeAtOffset}`); From 0bc276cdf5f45e010abe81c07d74b111fb0aadcf Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:10:08 +0600 Subject: [PATCH 080/144] fix: separate Canton package name from DAR package id --- src/orchestrator.mjs | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/orchestrator.mjs b/src/orchestrator.mjs index 5743c76..e65c9f9 100644 --- a/src/orchestrator.mjs +++ b/src/orchestrator.mjs @@ -39,7 +39,9 @@ function proofStep(name, submission, event) { } function roleClient(baseUrl, token, runtime) { - return new CantonJsonApi({ baseUrl, token, userId: runtime.userId || "", insecureLocal: runtime.insecureLocal || false }); + const client = new CantonJsonApi({ baseUrl, token, userId: runtime.userId || "", insecureLocal: runtime.insecureLocal || false }); + client.packageIdSelectionPreference = runtime.packageId ? [runtime.packageId] : []; + return client; } async function submitAndFind({ @@ -55,7 +57,8 @@ async function submitAndFind({ commands: [command], actAs: [actAs], workflowId, - commandId: `${workflowId}-${Date.now()}-${Math.random().toString(16).slice(2)}` + commandId: `${workflowId}-${Date.now()}-${Math.random().toString(16).slice(2)}`, + packageIdSelectionPreference: client.packageIdSelectionPreference || [] }); const event = await client.findActiveContract({ party: lookupParty, @@ -79,6 +82,7 @@ export function runtimeConfigFromEnv(env = process.env) { userId: env.CANTON_USER_ID || "", baseUrl, packageId: required(env.CANTON_PACKAGE_ID, "CANTON_PACKAGE_ID"), + packageName: required(env.CANTON_PACKAGE_NAME || "commit-ledger", "CANTON_PACKAGE_NAME"), parties: assertDistinctParties({ maintainer: env.CANTON_MAINTAINER_PARTY, contributor: env.CANTON_CONTRIBUTOR_PARTY, @@ -102,7 +106,7 @@ export async function runFullLifecycle({ githubToken = "", now = () => new Date() }) { - const { baseUrl, packageId, parties, tokens } = runtime; + const { baseUrl, packageId, packageName, parties, tokens } = runtime; const runId = randomUUID(); contributorGithub = required(contributorGithub, "contributorGithub"); const issue = await fetchGitHubIssue({ issueUrl, token: githubToken }); @@ -133,7 +137,8 @@ export async function runFullLifecycle({ const proof = { schemaVersion: 2, runId, - packageId, + packageName, + packageName, generatedAt: now().toISOString(), environment: runtime.insecureLocal ? "local-sandbox-no-auth" : "authenticated-ledger", issue, @@ -148,7 +153,7 @@ export async function runFullLifecycle({ const bountyCreated = await submitAndFind({ client: maintainerApi, command: buildCreateBountyCommand({ - packageId, + packageName, maintainer: parties.maintainer, verifier: parties.verifier, bounty @@ -156,7 +161,7 @@ export async function runFullLifecycle({ actAs: parties.maintainer, workflowId: "commitledger-create-bounty", lookupParty: parties.maintainer, - lookupTemplateId: templateId(packageId, "Bounty"), + lookupTemplateId: templateId(packageName, "Bounty"), predicate: arg => arg.bountyId === bounty.bountyId }); proof.steps.push(proofStep("BOUNTY_ON_LEDGER", bountyCreated.submission, bountyCreated.event)); @@ -164,7 +169,7 @@ export async function runFullLifecycle({ const claimCreated = await submitAndFind({ client: contributorApi, command: buildClaimRequestCommand({ - packageId, + packageName, contributor: parties.contributor, maintainer: parties.maintainer, bountyId: bounty.bountyId, @@ -173,7 +178,7 @@ export async function runFullLifecycle({ actAs: parties.contributor, workflowId: "commitledger-claim-request", lookupParty: parties.contributor, - lookupTemplateId: templateId(packageId, "ClaimRequest"), + lookupTemplateId: templateId(packageName, "ClaimRequest"), predicate: arg => arg.bountyId === bounty.bountyId && arg.contributorGithub === contributorGithub }); proof.steps.push(proofStep("CLAIM_REQUESTED", claimCreated.submission, claimCreated.event)); @@ -181,14 +186,14 @@ export async function runFullLifecycle({ const claimAccepted = await submitAndFind({ client: maintainerApi, command: buildAcceptClaimCommand({ - packageId, + packageName, claimRequestCid: claimCreated.event.contractId, bountyCid: bountyCreated.event.contractId }), actAs: parties.maintainer, workflowId: "commitledger-accept-claim", lookupParty: parties.maintainer, - lookupTemplateId: templateId(packageId, "ClaimedBounty"), + lookupTemplateId: templateId(packageName, "ClaimedBounty"), predicate: arg => arg.bountyId === bounty.bountyId && arg.contributorGithub === contributorGithub }); proof.steps.push(proofStep("CLAIMED", claimAccepted.submission, claimAccepted.event)); @@ -204,14 +209,14 @@ export async function runFullLifecycle({ const submitted = await submitAndFind({ client: contributorApi, command: buildSubmitPullRequestCommand({ - packageId, + packageName, claimedBountyCid: claimAccepted.event.contractId, pullRequest }), actAs: parties.contributor, workflowId: "commitledger-submit-pr", lookupParty: parties.contributor, - lookupTemplateId: templateId(packageId, "SubmittedBounty"), + lookupTemplateId: templateId(packageName, "SubmittedBounty"), predicate: arg => arg.bountyId === bounty.bountyId && Number(arg.pullRequest?.prNumber) === Number(prNumber) }); proof.steps.push(proofStep("PR_SUBMITTED", submitted.submission, submitted.event)); @@ -220,7 +225,7 @@ export async function runFullLifecycle({ "wrong issue evidence rejected", () => verifierApi.submitAndWait({ commands: [buildVerifyMergedCommand({ - packageId, + packageName, submittedBountyCid: submitted.event.contractId, evidence: { ...evidence, issueNumber: evidence.issueNumber + 1000 } })], @@ -234,14 +239,14 @@ export async function runFullLifecycle({ const verified = await submitAndFind({ client: verifierApi, command: buildVerifyMergedCommand({ - packageId, + packageName, submittedBountyCid: submitted.event.contractId, evidence }), actAs: parties.verifier, workflowId: "commitledger-verify-merge", lookupParty: parties.verifier, - lookupTemplateId: templateId(packageId, "VerifiedBounty"), + lookupTemplateId: templateId(packageName, "VerifiedBounty"), predicate: arg => arg.bountyId === bounty.bountyId && arg.evidence?.evidenceHash === evidence.evidenceHash }); proof.steps.push(proofStep("VERIFIED", verified.submission, verified.event)); @@ -252,7 +257,7 @@ export async function runFullLifecycle({ "unauthorized contributor settlement rejected", () => contributorApi.submitAndWait({ commands: [buildSettleCommand({ - packageId, + packageName, verifiedBountyCid: verified.event.contractId, settledAt, settlementRef: "unauthorized-attempt" @@ -267,7 +272,7 @@ export async function runFullLifecycle({ const settled = await submitAndFind({ client: maintainerApi, command: buildSettleCommand({ - packageId, + packageName, verifiedBountyCid: verified.event.contractId, settledAt, settlementRef @@ -275,7 +280,7 @@ export async function runFullLifecycle({ actAs: parties.maintainer, workflowId: "commitledger-settle", lookupParty: parties.maintainer, - lookupTemplateId: templateId(packageId, "SettlementReceipt"), + lookupTemplateId: templateId(packageName, "SettlementReceipt"), predicate: arg => arg.bountyId === bounty.bountyId && arg.evidenceHash === evidence.evidenceHash }); proof.steps.push(proofStep("SETTLED", settled.submission, settled.event)); @@ -285,7 +290,7 @@ export async function runFullLifecycle({ "duplicate settlement rejected", () => maintainerApi.submitAndWait({ commands: [buildSettleCommand({ - packageId, + packageName, verifiedBountyCid: verified.event.contractId, settledAt, settlementRef: "duplicate-attempt" From 0ae03cc9795fb761050470d2b69168f647ec023e Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:10:26 +0600 Subject: [PATCH 081/144] fix: inherit exact DAR package selection on every submission --- src/canton-json-api.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/canton-json-api.mjs b/src/canton-json-api.mjs index 1a929fd..6acd926 100644 --- a/src/canton-json-api.mjs +++ b/src/canton-json-api.mjs @@ -101,7 +101,7 @@ export class CantonJsonApi { return body; } - async submitAndWait({ commands, actAs, readAs = [], workflowId, commandId, packageIdSelectionPreference = [] }) { + async submitAndWait({ commands, actAs, readAs = [], workflowId, commandId, packageIdSelectionPreference = this.packageIdSelectionPreference || [] }) { return this.request("/v2/commands/submit-and-wait", { method: "POST", body: JSON.stringify({ From 31d1211227396fd6a63040feaedf59fdd04770bb Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:10:30 +0600 Subject: [PATCH 082/144] fix: preserve DAR id and package name in proof metadata --- src/orchestrator.mjs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/orchestrator.mjs b/src/orchestrator.mjs index e65c9f9..cfbccf3 100644 --- a/src/orchestrator.mjs +++ b/src/orchestrator.mjs @@ -106,7 +106,8 @@ export async function runFullLifecycle({ githubToken = "", now = () => new Date() }) { - const { baseUrl, packageId, packageName, parties, tokens } = runtime; + const { baseUrl, packageId, parties, tokens } = runtime; + const packageName = runtime.packageName || "commit-ledger"; const runId = randomUUID(); contributorGithub = required(contributorGithub, "contributorGithub"); const issue = await fetchGitHubIssue({ issueUrl, token: githubToken }); @@ -137,7 +138,7 @@ export async function runFullLifecycle({ const proof = { schemaVersion: 2, runId, - packageName, + packageId, packageName, generatedAt: now().toISOString(), environment: runtime.insecureLocal ? "local-sandbox-no-auth" : "authenticated-ledger", From 8947feaab0b39f3e35a690f0d7267a75aba5f0fa Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:10:37 +0600 Subject: [PATCH 083/144] fix: export Canton package name for local proof --- scripts/bootstrap-local.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/bootstrap-local.mjs b/scripts/bootstrap-local.mjs index 7e29e22..61988c6 100644 --- a/scripts/bootstrap-local.mjs +++ b/scripts/bootstrap-local.mjs @@ -8,7 +8,7 @@ import {CantonApiError} from '../src/ledger-errors.mjs'; const baseUrl=process.env.CANTON_JSON_API_URL || 'http://127.0.0.1:3975'; const api=new CantonJsonApi({baseUrl,insecureLocal:true}); const suffix=randomUUID().slice(0,8); -const env={CANTON_JSON_API_URL:baseUrl,CANTON_INSECURE_LOCAL:'true',CANTON_PACKAGE_ID:process.env.CANTON_PACKAGE_ID}; +const env={CANTON_JSON_API_URL:baseUrl,CANTON_INSECURE_LOCAL:'true',CANTON_PACKAGE_ID:process.env.CANTON_PACKAGE_ID,CANTON_PACKAGE_NAME:process.env.CANTON_PACKAGE_NAME || 'commit-ledger'}; if(!/^[a-f0-9]{64}$/.test(env.CANTON_PACKAGE_ID||'')) throw new Error('Actual DAR package ID is required'); async function allocateParty(role) { @@ -47,5 +47,5 @@ env.CANTON_USER_ID=userId; const quote=value=>"'"+String(value).replaceAll("'","'\\''")+"'"; await writeFile('.env.canton-demo.local',Object.entries(env).map(([k,v])=>`${k}=${quote(v)}`).join('\n')+'\n',{mode:0o600}); -await writeFile('evidence/ledger-setup.json',JSON.stringify({environment:'local-sandbox-no-auth',packageId:env.CANTON_PACKAGE_ID,parties,userId},null,2)+'\n'); +await writeFile('evidence/ledger-setup.json',JSON.stringify({environment:'local-sandbox-no-auth',packageId:env.CANTON_PACKAGE_ID,packageName:env.CANTON_PACKAGE_NAME,parties,userId},null,2)+'\n'); console.log('Allocated three real demo parties and one local demo user; wrote .env.canton-demo.local.'); From d1964ae18968fa51f0793b8f62006d2ea9e0bf7b Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:10:50 +0600 Subject: [PATCH 084/144] test: use Canton 3.5 package-name template references --- tests/canton-commands.test.mjs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/canton-commands.test.mjs b/tests/canton-commands.test.mjs index 64788fc..a533854 100644 --- a/tests/canton-commands.test.mjs +++ b/tests/canton-commands.test.mjs @@ -10,7 +10,7 @@ import { test("builds a Daml Bounty create command with Int fields encoded as strings", () => { const command = buildCreateBountyCommand({ - packageId: "pkg123", + packageName: "commit-ledger", maintainer: "Maintainer::1", verifier: "Verifier::1", bounty: { @@ -23,14 +23,14 @@ test("builds a Daml Bounty create command with Int fields encoded as strings", ( rewardUnit: "DEMO_CREDIT" } }); - assert.equal(command.CreateCommand.templateId, "pkg123:CommitLedger:Bounty"); + assert.equal(command.CreateCommand.templateId, "#commit-ledger:CommitLedger:Bounty"); assert.equal(command.CreateCommand.createArguments.issueNumber, "5"); assert.equal(command.CreateCommand.createArguments.rewardUnit, "DEMO_CREDIT"); }); test("encodes nested pull-request Int fields as strings for Daml values", () => { const command = buildSubmitPullRequestCommand({ - packageId: "pkg123", + packageName: "commit-ledger", claimedBountyCid: "cid-claimed", pullRequest: { repository: "Saidur-droid/CommitLedger", @@ -45,7 +45,7 @@ test("encodes nested pull-request Int fields as strings for Daml values", () => test("builds the explicit revision path", () => { const command = buildReturnForRevisionCommand({ - packageId: "pkg123", + packageName: "commit-ledger", submittedBountyCid: "cid-submitted", reason: "Update tests" }); @@ -67,7 +67,7 @@ test("binds issue-aware canonical merge evidence with Daml Int strings", () => { evidenceHash: "sha256:abc" }; const command = buildVerifyMergedCommand({ - packageId: "pkg123", + packageName: "commit-ledger", submittedBountyCid: "cid-submitted", evidence }); @@ -78,7 +78,7 @@ test("binds issue-aware canonical merge evidence with Daml Int strings", () => { test("settlement is an explicit consuming Daml choice", () => { const command = buildSettleCommand({ - packageId: "pkg123", + packageName: "commit-ledger", verifiedBountyCid: "cid-verified", settledAt: "2026-09-27T10:00:00Z", settlementRef: "settlement-001" From 8517d29959b6ab9d8398f439c97cd5a903d45374 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:10:54 +0600 Subject: [PATCH 085/144] test: cover Canton package-name refs and DAR pinning --- tests/security-regressions.test.mjs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/security-regressions.test.mjs b/tests/security-regressions.test.mjs index 602f5b9..0f05efd 100644 --- a/tests/security-regressions.test.mjs +++ b/tests/security-regressions.test.mjs @@ -101,20 +101,22 @@ test('Canton client preserves structured error details', async t => { test('Canton commands use v2 userId and synchronizerId names', async t => { let body; t.mock.method(globalThis, 'fetch', async (_url, options) => {body=JSON.parse(options.body);return Response.json({});}); - await new CantonJsonApi({baseUrl:'http://localhost:3975',token:'unit-token'}).submitAndWait({commands:[],actAs:[],commandId:'unit'}); + const client=new CantonJsonApi({baseUrl:'http://localhost:3975',token:'unit-token'}); client.packageIdSelectionPreference=['a'.repeat(64)]; + await client.submitAndWait({commands:[],actAs:[],commandId:'unit'}); assert.ok('userId' in body && 'synchronizerId' in body); assert.equal('applicationId' in body, false); assert.equal('domainId' in body, false); + assert.deepEqual(body.packageIdSelectionPreference,['a'.repeat(64)]); }); test('insecure sandbox configuration cannot target a remote server', () => { assert.throws(() => new CantonJsonApi({baseUrl:'https://remote.example',insecureLocal:true}), /loopback/); assert.throws(() => runtimeConfigFromEnv({CANTON_JSON_API_URL:'https://remote.example',CANTON_INSECURE_LOCAL:'true'}), /loopback/); }); test('ambiguous active contracts cannot silently choose an old demo receipt', async t => { - const entry = {contractEntry:{JsActiveContract:{createdEvent:{contractId:'unit-cid',templateId:'pkg:CommitLedger:Bounty',createArgument:{}}}}}; + const entry = {contractEntry:{JsActiveContract:{createdEvent:{contractId:'unit-cid',templateId:'a'.repeat(64)+':CommitLedger:Bounty',createArgument:{}}}}}; t.mock.method(globalThis, 'fetch', async () => Response.json([entry,entry])); const api=new CantonJsonApi({baseUrl:'http://localhost:3975',token:'unit-token'}); - await assert.rejects(api.findActiveContract({party:'unit-party',templateId:'pkg:CommitLedger:Bounty',activeAtOffset:1}), /Ambiguous/); + await assert.rejects(api.findActiveContract({party:'unit-party',templateId:'#commit-ledger:CommitLedger:Bounty',activeAtOffset:1}), /Ambiguous/); }); @@ -131,7 +133,7 @@ test('submit-and-wait normalizes nested Daml numeric values to JSON strings', as t.mock.method(globalThis, 'fetch', async (_url, options) => { body = JSON.parse(options.body); return Response.json({}); }); const api = new CantonJsonApi({baseUrl:'http://localhost:3975',token:'unit-token'}); await api.submitAndWait({ - commands:[{CreateCommand:{templateId:'pkg:CommitLedger:Bounty',createArguments:{issueNumber:5,rewardAmount:100.0,nested:{prNumber:6},items:[7]}}}], + commands:[{CreateCommand:{templateId:'#commit-ledger:CommitLedger:Bounty',createArguments:{issueNumber:5,rewardAmount:100.0,nested:{prNumber:6},items:[7]}}}], actAs:['Maintainer::1'], commandId:'unit-numeric-normalization' }); From fcdf2c7ebdf8a7fb6e962114d0a4c0c0c2a386cd Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:11:14 +0600 Subject: [PATCH 086/144] fix: derive Daml package name for Canton proof --- scripts/run-local-proof.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/run-local-proof.sh b/scripts/run-local-proof.sh index ffe3583..028ac68 100755 --- a/scripts/run-local-proof.sh +++ b/scripts/run-local-proof.sh @@ -39,6 +39,12 @@ test -f "$DAR" export CANTON_PACKAGE_ID CANTON_PACKAGE_ID=$(python3 scripts/dar-package-id.py "$DAR") +export CANTON_PACKAGE_NAME +CANTON_PACKAGE_NAME=$(sed -n 's/^name:[[:space:]]*//p' daml/daml.yaml | head -n 1 | tr -d '\r') +if [ -z "$CANTON_PACKAGE_NAME" ]; then + echo 'Could not determine Daml package name from daml/daml.yaml.' >&2 + exit 1 +fi CANTON_JSON_API_PORT=$(python3 - <<'PY' import socket From 1ce3801610d472cb2384e89a3f19f85969caca7d Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:27:30 +0600 Subject: [PATCH 087/144] fix: eliminate inherited errexit from verification pipeline --- scripts/verify-all.sh | 73 ++++++++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 25 deletions(-) diff --git a/scripts/verify-all.sh b/scripts/verify-all.sh index 9c0a633..b7615c4 100755 --- a/scripts/verify-all.sh +++ b/scripts/verify-all.sh @@ -1,31 +1,22 @@ #!/usr/bin/env bash +set +e set -u -cd "$(dirname "$0")/.." +cd "$(dirname "$0")/.." || exit 1 mkdir -p evidence rm -f evidence/verification.json export PATH="${DPM_HOME:-$HOME/.dpm}/bin:$PATH" export DPM_SDK_VERSION=3.5.12 SOURCE_COMMIT=$(git rev-parse HEAD) -run_logged() { - local label=$1 - local logfile=$2 - shift 2 - echo "=== $label ===" - "$@" >"$logfile" 2>&1 - local status=$? - cat "$logfile" - if [ "$status" -ne 0 ]; then - echo "=== $label: FAILED with status $status ===" >&2 - return "$status" - fi - echo "=== $label: PASS ===" - return 0 -} - -run_logged "STAGE 1/4: Node" evidence/node-tests.log npm test +echo "=== STAGE 1/4: Node ===" +npm test > evidence/node-tests.log 2>&1 status=$? -if [ "$status" -ne 0 ]; then exit "$status"; fi +cat evidence/node-tests.log +if [ "$status" -ne 0 ]; then + echo "=== STAGE 1/4: Node: FAILED with status $status ===" >&2 + exit "$status" +fi +echo "=== STAGE 1/4: Node: PASS ===" echo "=== STAGE 2/4: DPM ===" if ! command -v dpm >/dev/null 2>&1; then @@ -33,17 +24,49 @@ if ! command -v dpm >/dev/null 2>&1; then cat evidence/daml-build.log exit 2 fi -run_logged "STAGE 2/4: DPM" evidence/dpm-version.log bash -c 'cd daml && dpm version --active' +( + cd daml || exit 1 + dpm version --active +) > evidence/dpm-version.log 2>&1 status=$? -if [ "$status" -ne 0 ]; then exit "$status"; fi +cat evidence/dpm-version.log +if [ "$status" -ne 0 ]; then + echo "=== STAGE 2/4: DPM: FAILED with status $status ===" >&2 + exit "$status" +fi +echo "=== STAGE 2/4: DPM: PASS ===" -run_logged "STAGE 3/4: Daml build" evidence/daml-build.log bash -c 'cd daml && dpm build' +echo "=== STAGE 3/4: Daml build ===" +( + cd daml || exit 1 + dpm build +) > evidence/daml-build.log 2>&1 status=$? -if [ "$status" -ne 0 ]; then exit "$status"; fi +cat evidence/daml-build.log +if [ "$status" -ne 0 ]; then + echo "=== STAGE 3/4: Daml build: FAILED with status $status ===" >&2 + exit "$status" +fi +echo "=== STAGE 3/4: Daml build: PASS ===" -run_logged "STAGE 4/4: Daml tests" evidence/daml-tests.log bash -c 'cd daml && dpm test' +echo "=== STAGE 4/4: Daml tests ===" +( + cd daml || exit 1 + dpm test +) > evidence/daml-tests.log 2>&1 status=$? -if [ "$status" -ne 0 ]; then exit "$status"; fi +cat evidence/daml-tests.log +if [ "$status" -ne 0 ]; then + echo "=== STAGE 4/4: Daml tests: FAILED with status $status ===" >&2 + exit "$status" +fi +echo "=== STAGE 4/4: Daml tests: PASS ===" SOURCE_COMMIT="$SOURCE_COMMIT" node -e 'require("node:fs").writeFileSync("evidence/verification.json",JSON.stringify({node:true,damlBuild:true,damlTests:true,verifiedAt:new Date().toISOString(),commit:process.env.SOURCE_COMMIT},null,2)+"\n")' +status=$? +if [ "$status" -ne 0 ]; then + echo "=== EVIDENCE WRITE: FAILED with status $status ===" >&2 + exit "$status" +fi echo "=== BUILD/TEST VERIFICATION COMPLETE ===" +exit 0 From 9c787e9be91c9d8d2b88d7c6a8780c4eb440e3fd Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:27:42 +0600 Subject: [PATCH 088/144] test: assert explicit non-errexit verification flow --- tests/proof-wrapper-static.test.mjs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/proof-wrapper-static.test.mjs b/tests/proof-wrapper-static.test.mjs index 68a2724..e36d276 100644 --- a/tests/proof-wrapper-static.test.mjs +++ b/tests/proof-wrapper-static.test.mjs @@ -19,11 +19,14 @@ test('verification prints explicit stage markers', () => { } }); -test('verification advances stages with explicit status checks and no shell errexit', () => { +test('verification explicitly disables errexit and checks every stage status', () => { + assert.match(verify, /^set \+e$/m); assert.doesNotMatch(verify, /^set\s+-[A-Za-z]*e[A-Za-z]*\s*(?:#.*)?$/m); - assert.match(verify, /run_logged/); - assert.match(verify, /local status=\$\?/); + assert.match(verify, /npm test > evidence\/node-tests\.log 2>&1/); + assert.match(verify, /status=\$\?/); assert.match(verify, /if \[ "\$status" -ne 0 \]; then/); - assert.match(verify, /FAILED with status/); + assert.match(verify, /STAGE 1\/4: Node: PASS/); + assert.match(verify, /STAGE 4\/4: Daml tests: PASS/); assert.match(verify, /BUILD\/TEST VERIFICATION COMPLETE/); + assert.match(verify, /exit 0/); }); From 807648675c912a531291ffdcf22deb22217e6f55 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:30:25 +0600 Subject: [PATCH 089/144] fix: consume created contracts from submit transaction response --- src/canton-json-api.mjs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/canton-json-api.mjs b/src/canton-json-api.mjs index 6acd926..7931395 100644 --- a/src/canton-json-api.mjs +++ b/src/canton-json-api.mjs @@ -74,6 +74,14 @@ export function extractCreatedEvents(activeContractsResponse) { .filter(Boolean); } +export function extractTransactionCreatedEvents(response) { + const events = response?.transaction?.events; + if (!Array.isArray(events)) return []; + return events + .map(event => event?.CreatedEvent?.value || event?.CreatedEvent) + .filter(Boolean); +} + export class CantonJsonApi { constructor({ baseUrl, token, userId = "", insecureLocal = false }) { this.baseUrl = requireText(baseUrl, "Canton base URL").replace(/\/$/, ""); @@ -120,6 +128,27 @@ export class CantonJsonApi { }); } + async submitAndWaitForTransaction({ commands, actAs, readAs = [], workflowId, commandId, packageIdSelectionPreference = this.packageIdSelectionPreference || [] }) { + return this.request("/v2/commands/submit-and-wait-for-transaction", { + method: "POST", + body: JSON.stringify({ + commands: { + commands: commands.map(encodeCommandDamlValues), + workflowId, + userId: this.userId, + commandId, + deduplicationPeriod: { Empty: {} }, + actAs, + readAs, + submissionId: commandId, + disclosedContracts: [], + synchronizerId: "", + packageIdSelectionPreference + } + }) + }); + } + async activeContracts({ party, templateId, activeAtOffset }) { return this.request("/v2/state/active-contracts", { method: "POST", From 8f56e87b6fee9f1eebb6fe4ca71c6dc6be103ac1 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:32:57 +0600 Subject: [PATCH 090/144] fix: chain proof directly from committed transaction events --- src/orchestrator.mjs | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/orchestrator.mjs b/src/orchestrator.mjs index cfbccf3..9d259db 100644 --- a/src/orchestrator.mjs +++ b/src/orchestrator.mjs @@ -1,6 +1,6 @@ import { randomUUID } from "node:crypto"; import { expectLedgerFailure } from "./ledger-errors.mjs"; -import { CantonJsonApi } from "./canton-json-api.mjs"; +import { CantonJsonApi, extractTransactionCreatedEvents } from "./canton-json-api.mjs"; import { templateId, buildCreateBountyCommand, @@ -53,20 +53,31 @@ async function submitAndFind({ lookupTemplateId, predicate }) { - const submission = await client.submitAndWait({ + const response = await client.submitAndWaitForTransaction({ commands: [command], actAs: [actAs], + readAs: [lookupParty], workflowId, commandId: `${workflowId}-${Date.now()}-${Math.random().toString(16).slice(2)}`, packageIdSelectionPreference: client.packageIdSelectionPreference || [] }); - const event = await client.findActiveContract({ - party: lookupParty, - templateId: lookupTemplateId, - activeAtOffset: offsetOf(submission), - predicate + const transaction = response?.transaction; + const offset = Number(transaction?.offset); + if (!transaction?.updateId || !Number.isSafeInteger(offset) || offset <= 0) { + throw new Error("Canton transaction response did not return updateId and offset"); + } + const requestedEntity = String(lookupTemplateId).split(":").slice(-2).join(":"); + const matches = extractTransactionCreatedEvents(response).filter(candidate => { + const candidateEntity = String(candidate?.templateId || "").split(":").slice(-2).join(":"); + return candidateEntity === requestedEntity && predicate(candidate?.createArgument || {}); }); - return { submission, event }; + if (matches.length !== 1) { + throw new Error(`Expected exactly one created ${requestedEntity} contract, found ${matches.length}`); + } + return { + submission: { updateId: transaction.updateId, completionOffset: offset }, + event: matches[0] + }; } export function runtimeConfigFromEnv(env = process.env) { From befa6b878c6e2d7ad190bb193488b24945bf3a21 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:33:47 +0600 Subject: [PATCH 091/144] test: mirror Canton submit-and-wait transaction responses --- tests/lifecycle-transport.test.mjs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/lifecycle-transport.test.mjs b/tests/lifecycle-transport.test.mjs index f973d8d..de5cf50 100644 --- a/tests/lifecycle-transport.test.mjs +++ b/tests/lifecycle-transport.test.mjs @@ -4,7 +4,7 @@ import assert from 'node:assert/strict'; import {runFullLifecycle} from '../src/orchestrator.mjs'; const repository='Saidur-droid/CommitLedger'; const parties={maintainer:'Maintainer::unit',contributor:'Contributor::unit',verifier:'Verifier::unit'}; -const runtime={baseUrl:'http://localhost:3975',packageId:'unit-package',parties,tokens:{maintainer:'unit-maintainer',contributor:'unit-contributor',verifier:'unit-verifier'}}; +const runtime={baseUrl:'http://localhost:3975',packageId:'a'.repeat(64),packageName:'commit-ledger',parties,tokens:{maintainer:'unit-maintainer',contributor:'unit-contributor',verifier:'unit-verifier'}}; const input={runtime,issueUrl:`https://github.com/${repository}/issues/5`,contributorGithub:'Saidur-droid',prNumber:6}; function installTransport(t, {unmerged=false,negativeNetworkFailure=false}={}) { const active=new Map(); const commands=[]; let seq=0; @@ -17,12 +17,11 @@ function installTransport(t, {unmerged=false,negativeNetworkFailure=false}={}) { throw new Error(`Unexpected GitHub URL: ${url}`); } const body=JSON.parse(options.body); - if(url.endsWith('/v2/state/active-contracts')) { - return Response.json([...active.values()].map(createdEvent=>({contractEntry:{JsActiveContract:{createdEvent}}}))); - } - assert.ok(url.endsWith('/v2/commands/submit-and-wait')); - commands.push(body); - const command=body.commands[0]; let name,args; + const transactionMode=url.endsWith('/v2/commands/submit-and-wait-for-transaction'); + assert.ok(transactionMode || url.endsWith('/v2/commands/submit-and-wait')); + const payload=transactionMode ? body.commands : body; + commands.push(payload); + const command=payload.commands[0]; let name,args; if(command.CreateCommand) { name=command.CreateCommand.templateId.split(':').at(-1); args=command.CreateCommand.createArguments; @@ -32,30 +31,31 @@ function installTransport(t, {unmerged=false,negativeNetworkFailure=false}={}) { if(!previous) return Response.json({code:'CONTRACT_NOT_FOUND',cause:`Contract ${ex.contractId} already consumed`},{status:404}); args={...previous.createArgument}; if(ex.choice==='ClaimRequest_Accept') { - assert.equal(body.actAs[0],parties.maintainer); + assert.equal(payload.actAs[0],parties.maintainer); const bounty=active.get(ex.choiceArgument.bountyCid); assert.ok(bounty,'exact prior bounty CID must be carried forward'); args={...bounty.createArgument,contributor:args.contributor,contributorGithub:args.contributorGithub}; active.delete(ex.choiceArgument.bountyCid); name='ClaimedBounty'; } else if(ex.choice==='ClaimedBounty_SubmitPullRequest') { - assert.equal(body.actAs[0],parties.contributor); + assert.equal(payload.actAs[0],parties.contributor); args.pullRequest=ex.choiceArgument.pullRequest; name='SubmittedBounty'; } else if(ex.choice==='SubmittedBounty_VerifyMerged') { - assert.equal(body.actAs[0],parties.verifier); + assert.equal(payload.actAs[0],parties.verifier); if(ex.choiceArgument.evidence.issueNumber!==args.issueNumber) { if(negativeNetworkFailure) throw new TypeError('fetch failed'); return Response.json({code:'DAML_UNHANDLED_EXCEPTION',cause:'evidence issue number mismatch'},{status:400}); } args.evidence=ex.choiceArgument.evidence;name='VerifiedBounty'; } else if(ex.choice==='VerifiedBounty_Settle') { - if(body.actAs[0]!==parties.maintainer) return Response.json({code:'DAML_AUTHORIZATION_ERROR',cause:'wrong controller'},{status:400}); + if(payload.actAs[0]!==parties.maintainer) return Response.json({code:'DAML_AUTHORIZATION_ERROR',cause:'wrong controller'},{status:400}); args={...args,...ex.choiceArgument,evidenceHash:args.evidence.evidenceHash,mergeCommitSha:args.evidence.mergeCommitSha};name='SettlementReceipt'; } else throw new Error(`Unexpected choice ${ex.choice}`); active.delete(ex.contractId); } seq++; - const event={contractId:`unit-contract-${seq}`,templateId:`unit-package:CommitLedger:${name}`,createArgument:args}; + const event={contractId:`unit-contract-${seq}`,templateId:`${'a'.repeat(64)}:CommitLedger:${name}`,createArgument:args}; active.set(event.contractId,event); + if(transactionMode) return Response.json({transaction:{updateId:`unit-update-${seq}`,offset:seq,events:[{CreatedEvent:{value:event}}]}}); return Response.json({updateId:`unit-update-${seq}`,completionOffset:seq}); }); return {commands,active}; From 5155df64fa52615a32f48bf2251f27a8bc529ba3 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:33:50 +0600 Subject: [PATCH 092/144] test: cover Canton transaction created-event decoding --- tests/canton-json-api.test.mjs | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/tests/canton-json-api.test.mjs b/tests/canton-json-api.test.mjs index a4c77e9..bf76ec4 100644 --- a/tests/canton-json-api.test.mjs +++ b/tests/canton-json-api.test.mjs @@ -1,11 +1,11 @@ import test from "node:test"; import assert from "node:assert/strict"; -import { eventFormatForParty, extractCreatedEvents } from "../src/canton-json-api.mjs"; +import { eventFormatForParty, extractCreatedEvents, extractTransactionCreatedEvents } from "../src/canton-json-api.mjs"; test("builds a template-scoped Canton active-contract filter", () => { - const format = eventFormatForParty("Maintainer::1", "pkg:CommitLedger:Bounty"); + const format = eventFormatForParty("Maintainer::1", "#commit-ledger:CommitLedger:Bounty"); const filter = format.filtersByParty["Maintainer::1"].cumulative[0].identifierFilter.TemplateFilter.value; - assert.equal(filter.templateId, "pkg:CommitLedger:Bounty"); + assert.equal(filter.templateId, "#commit-ledger:CommitLedger:Bounty"); assert.equal(filter.includeCreatedEventBlob, false); assert.equal(format.verbose, true); }); @@ -17,7 +17,7 @@ test("extracts active created events from Canton JSON API responses", () => { JsActiveContract: { createdEvent: { contractId: "00abc", - templateId: "pkg:CommitLedger:Bounty", + templateId: "#commit-ledger:CommitLedger:Bounty", createArgument: { bountyId: "bounty-5" } } } @@ -29,3 +29,21 @@ test("extracts active created events from Canton JSON API responses", () => { assert.equal(events[0].contractId, "00abc"); assert.equal(events[0].createArgument.bountyId, "bounty-5"); }); + + +test("extracts created events from submit-and-wait transaction responses", () => { + const events = extractTransactionCreatedEvents({ + transaction: { + updateId: "unit-update", + offset: 1, + events: [{ CreatedEvent: { value: { + contractId: "00created", + templateId: "a".repeat(64) + ":CommitLedger:Bounty", + createArgument: { bountyId: "bounty-5" } + } } }] + } + }); + assert.equal(events.length, 1); + assert.equal(events[0].contractId, "00created"); + assert.equal(events[0].createArgument.bountyId, "bounty-5"); +}); From bf5ac23399417424567f9f55506a670d3ccf92c2 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:33:52 +0600 Subject: [PATCH 093/144] test: lock default Daml package name in runtime config --- tests/orchestrator.test.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/orchestrator.test.mjs b/tests/orchestrator.test.mjs index a5ff97d..a833afb 100644 --- a/tests/orchestrator.test.mjs +++ b/tests/orchestrator.test.mjs @@ -22,6 +22,7 @@ test("runtime config supports one local token or role-specific tokens", () => { CANTON_CONTRIBUTOR_PARTY: "Contributor::1", CANTON_VERIFIER_PARTY: "Verifier::1" }); + assert.equal(config.packageName, "commit-ledger"); assert.equal(config.tokens.maintainer, "local-admin-token"); assert.equal(config.tokens.contributor, "local-admin-token"); assert.equal(config.tokens.verifier, "local-admin-token"); From a7177d9a03b7277d986820a419d979233ccbb306 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:35:02 +0600 Subject: [PATCH 094/144] fix: validate package identity in final evidence bundle --- scripts/check-evidence.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/check-evidence.mjs b/scripts/check-evidence.mjs index 815879b..acf2006 100644 --- a/scripts/check-evidence.mjs +++ b/scripts/check-evidence.mjs @@ -6,12 +6,13 @@ if(!verification.node || !verification.damlBuild || !verification.damlTests) thr if(!/^[a-f0-9]{40}$/.test(verification.commit||'')) throw new Error('Verification must be bound to an exact Git commit'); const proof=JSON.parse(await readFile('evidence/canton-proof.json','utf8')); -if(proof.schemaVersion!==2 || !/^[a-f0-9]{64}$/.test(proof.packageId||'')) throw new Error('Real package ID and current proof schema are required'); +if(proof.schemaVersion!==2 || !/^[a-f0-9]{64}$/.test(proof.packageId||'') || !/^[A-Za-z0-9_.-]+$/.test(proof.packageName||'')) throw new Error('Real package ID, package name and current proof schema are required'); if(proof.sourceCommit!==verification.commit) throw new Error('Node/Daml verification and Canton proof must use the same source commit'); const names=['BOUNTY_ON_LEDGER','CLAIM_REQUESTED','CLAIMED','PR_SUBMITTED','VERIFIED','SETTLED']; if(proof.steps?.length!==6 || proof.steps.some((s,i)=>s.name!==names[i] || !s.updateId || !s.contractId || !Number.isSafeInteger(s.completionOffset))) throw new Error('Six complete ledger transitions are required'); if(new Set(proof.steps.map(s=>s.contractId)).size!==6) throw new Error('Contract IDs must be distinct'); +if(proof.steps.some(s=>String(s.templateId||'').split(':').length<3)) throw new Error('Every proof step must retain its ledger template identifier'); if(proof.negativeChecks?.length!==3 || proof.negativeChecks.some(c=>!c.rejected || !c.code || !c.response)) throw new Error('Three structured rejection responses are required'); if(proof.settlementReceipt?.rewardUnit!=='DEMO_CREDIT') throw new Error('Expected clearly labelled demo credit receipt'); const hash=buildMergeEvidence(proof.mergeEvidence).evidenceHash; From 0a52f8090908495f7e18c871c47ed10dfb9e5f75 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:35:06 +0600 Subject: [PATCH 095/144] fix: expose deterministic Canton proof stage failures --- scripts/run-local-proof.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/scripts/run-local-proof.sh b/scripts/run-local-proof.sh index 028ac68..9f50704 100755 --- a/scripts/run-local-proof.sh +++ b/scripts/run-local-proof.sh @@ -96,8 +96,23 @@ source .env.canton-demo.local set +a export COMMITLEDGER_EVIDENCE_FILE="$PWD/evidence/canton-proof.json" +echo "=== STAGE 5/6: Real Canton lifecycle ===" npm run demo:full 2>&1 | tee evidence/canton-demo.log +status=${PIPESTATUS[0]} +if [ "$status" -ne 0 ]; then + echo "=== STAGE 5/6: Real Canton lifecycle: FAILED with status $status ===" >&2 + exit "$status" +fi +echo "=== STAGE 5/6: Real Canton lifecycle: PASS ===" + +echo "=== STAGE 6/6: Evidence validation ===" node scripts/check-evidence.mjs +status=$? +if [ "$status" -ne 0 ]; then + echo "=== STAGE 6/6: Evidence validation: FAILED with status $status ===" >&2 + exit "$status" +fi +echo "=== STAGE 6/6: Evidence validation: PASS ===" if [ "${COMMITLEDGER_SERVE_AFTER_PROOF:-false}" = true ]; then npm start From 4e6852154b24ed6b41ba326ca5b5baa9a87ca443 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:35:33 +0600 Subject: [PATCH 096/144] fix: keep transaction visibility payload minimal --- src/orchestrator.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/orchestrator.mjs b/src/orchestrator.mjs index 9d259db..4a3651c 100644 --- a/src/orchestrator.mjs +++ b/src/orchestrator.mjs @@ -56,7 +56,6 @@ async function submitAndFind({ const response = await client.submitAndWaitForTransaction({ commands: [command], actAs: [actAs], - readAs: [lookupParty], workflowId, commandId: `${workflowId}-${Date.now()}-${Math.random().toString(16).slice(2)}`, packageIdSelectionPreference: client.packageIdSelectionPreference || [] From 0e23dd1f742fd2fe111b3f8eb42bb4a502115bb0 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:40:11 +0600 Subject: [PATCH 097/144] fix: replace shell verification with deterministic Node runner --- scripts/verify-all.mjs | 88 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 scripts/verify-all.mjs diff --git a/scripts/verify-all.mjs b/scripts/verify-all.mjs new file mode 100644 index 0000000..d47b7da --- /dev/null +++ b/scripts/verify-all.mjs @@ -0,0 +1,88 @@ +import { spawnSync } from "node:child_process"; +import { mkdirSync, rmSync, writeFileSync } from "node:fs"; +import { resolve } from "node:path"; + +const root = resolve(new URL("..", import.meta.url).pathname, ".."); +process.chdir(root); +mkdirSync("evidence", { recursive: true }); +rmSync("evidence/verification.json", { force: true }); + +const env = { + ...process.env, + PATH: `${process.env.DPM_HOME || `${process.env.HOME}/.dpm`}/bin:${process.env.PATH || ""}`, + DPM_SDK_VERSION: "3.5.12" +}; + +function run(label, command, args, { cwd = root, logFile } = {}) { + process.stdout.write(`=== ${label} ===\n`); + const result = spawnSync(command, args, { + cwd, + env, + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"] + }); + const output = `${result.stdout || ""}${result.stderr || ""}`; + if (logFile) writeFileSync(logFile, output); + process.stdout.write(output); + if (result.error) { + process.stderr.write(`=== ${label}: FAILED to launch: ${result.error.message} ===\n`); + process.exit(127); + } + if (result.status !== 0) { + process.stderr.write(`=== ${label}: FAILED with status ${result.status} ===\n`); + process.exit(result.status ?? 1); + } + process.stdout.write(`=== ${label}: PASS ===\n`); +} + +const source = spawnSync("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8" }); +if (source.status !== 0) { + process.stderr.write(source.stderr || "Unable to resolve Git HEAD\n"); + process.exit(source.status ?? 1); +} +const sourceCommit = source.stdout.trim(); + +run("STAGE 1/4: Node", "npm", ["test"], { logFile: "evidence/node-tests.log" }); + +const dpmCheck = spawnSync("dpm", ["version", "--active"], { + cwd: resolve(root, "daml"), + env, + encoding: "utf8" +}); +if (dpmCheck.error?.code === "ENOENT") { + const message = "BLOCKED: DPM is not installed. Run bash scripts/bootstrap-dpm.sh on an internet-connected development machine.\n"; + writeFileSync("evidence/daml-build.log", message); + process.stderr.write(message); + process.exit(2); +} +const dpmOutput = `${dpmCheck.stdout || ""}${dpmCheck.stderr || ""}`; +writeFileSync("evidence/dpm-version.log", dpmOutput); +process.stdout.write("=== STAGE 2/4: DPM ===\n"); +process.stdout.write(dpmOutput); +if (dpmCheck.status !== 0) { + process.stderr.write(`=== STAGE 2/4: DPM: FAILED with status ${dpmCheck.status} ===\n`); + process.exit(dpmCheck.status ?? 1); +} +process.stdout.write("=== STAGE 2/4: DPM: PASS ===\n"); + +run("STAGE 3/4: Daml build", "dpm", ["build"], { + cwd: resolve(root, "daml"), + logFile: "evidence/daml-build.log" +}); +run("STAGE 4/4: Daml tests", "dpm", ["test"], { + cwd: resolve(root, "daml"), + logFile: "evidence/daml-tests.log" +}); + +writeFileSync( + "evidence/verification.json", + JSON.stringify({ + node: true, + damlBuild: true, + damlTests: true, + verifiedAt: new Date().toISOString(), + commit: sourceCommit + }, null, 2) + "\n" +); + +process.stdout.write("=== BUILD/TEST VERIFICATION COMPLETE ===\n"); From 0ae4655b301f22c1d20a8b898bc65cf025673e92 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:40:20 +0600 Subject: [PATCH 098/144] fix: run proof verification through Node process runner --- scripts/run-local-proof.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/run-local-proof.sh b/scripts/run-local-proof.sh index 9f50704..fdecda6 100755 --- a/scripts/run-local-proof.sh +++ b/scripts/run-local-proof.sh @@ -32,7 +32,7 @@ if [ -z "${GITHUB_TOKEN:-}" ] && command -v gh >/dev/null 2>&1; then export GITHUB_TOKEN fi -bash scripts/verify-all.sh +node scripts/verify-all.mjs DAR="$PWD/daml/.daml/dist/commit-ledger-0.1.0.dar" test -f "$DAR" From 6504101047bbf422b036a4ee6ec21d2e36b60b9e Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:40:23 +0600 Subject: [PATCH 099/144] fix: reduce shell verifier to Node compatibility wrapper --- scripts/verify-all.sh | 71 +------------------------------------------ 1 file changed, 1 insertion(+), 70 deletions(-) diff --git a/scripts/verify-all.sh b/scripts/verify-all.sh index b7615c4..276ced3 100755 --- a/scripts/verify-all.sh +++ b/scripts/verify-all.sh @@ -1,72 +1,3 @@ #!/usr/bin/env bash -set +e -set -u cd "$(dirname "$0")/.." || exit 1 -mkdir -p evidence -rm -f evidence/verification.json -export PATH="${DPM_HOME:-$HOME/.dpm}/bin:$PATH" -export DPM_SDK_VERSION=3.5.12 -SOURCE_COMMIT=$(git rev-parse HEAD) - -echo "=== STAGE 1/4: Node ===" -npm test > evidence/node-tests.log 2>&1 -status=$? -cat evidence/node-tests.log -if [ "$status" -ne 0 ]; then - echo "=== STAGE 1/4: Node: FAILED with status $status ===" >&2 - exit "$status" -fi -echo "=== STAGE 1/4: Node: PASS ===" - -echo "=== STAGE 2/4: DPM ===" -if ! command -v dpm >/dev/null 2>&1; then - echo 'BLOCKED: DPM is not installed. Run bash scripts/bootstrap-dpm.sh on an internet-connected development machine.' > evidence/daml-build.log - cat evidence/daml-build.log - exit 2 -fi -( - cd daml || exit 1 - dpm version --active -) > evidence/dpm-version.log 2>&1 -status=$? -cat evidence/dpm-version.log -if [ "$status" -ne 0 ]; then - echo "=== STAGE 2/4: DPM: FAILED with status $status ===" >&2 - exit "$status" -fi -echo "=== STAGE 2/4: DPM: PASS ===" - -echo "=== STAGE 3/4: Daml build ===" -( - cd daml || exit 1 - dpm build -) > evidence/daml-build.log 2>&1 -status=$? -cat evidence/daml-build.log -if [ "$status" -ne 0 ]; then - echo "=== STAGE 3/4: Daml build: FAILED with status $status ===" >&2 - exit "$status" -fi -echo "=== STAGE 3/4: Daml build: PASS ===" - -echo "=== STAGE 4/4: Daml tests ===" -( - cd daml || exit 1 - dpm test -) > evidence/daml-tests.log 2>&1 -status=$? -cat evidence/daml-tests.log -if [ "$status" -ne 0 ]; then - echo "=== STAGE 4/4: Daml tests: FAILED with status $status ===" >&2 - exit "$status" -fi -echo "=== STAGE 4/4: Daml tests: PASS ===" - -SOURCE_COMMIT="$SOURCE_COMMIT" node -e 'require("node:fs").writeFileSync("evidence/verification.json",JSON.stringify({node:true,damlBuild:true,damlTests:true,verifiedAt:new Date().toISOString(),commit:process.env.SOURCE_COMMIT},null,2)+"\n")' -status=$? -if [ "$status" -ne 0 ]; then - echo "=== EVIDENCE WRITE: FAILED with status $status ===" >&2 - exit "$status" -fi -echo "=== BUILD/TEST VERIFICATION COMPLETE ===" -exit 0 +exec node scripts/verify-all.mjs From 9a477ab6f609c0f70c42a7a36157d79a8e89812b Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:40:36 +0600 Subject: [PATCH 100/144] test: lock Node-owned verification architecture --- tests/proof-wrapper-static.test.mjs | 34 +++++++++++++++++------------ 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/tests/proof-wrapper-static.test.mjs b/tests/proof-wrapper-static.test.mjs index e36d276..1275de4 100644 --- a/tests/proof-wrapper-static.test.mjs +++ b/tests/proof-wrapper-static.test.mjs @@ -3,7 +3,9 @@ import assert from 'node:assert/strict'; import fs from 'node:fs/promises'; const wrapper = await fs.readFile(new URL('../scripts/prove-and-report.sh', import.meta.url), 'utf8'); -const verify = await fs.readFile(new URL('../scripts/verify-all.sh', import.meta.url), 'utf8'); +const shellVerify = await fs.readFile(new URL('../scripts/verify-all.sh', import.meta.url), 'utf8'); +const nodeVerify = await fs.readFile(new URL('../scripts/verify-all.mjs', import.meta.url), 'utf8'); +const runner = await fs.readFile(new URL('../scripts/run-local-proof.sh', import.meta.url), 'utf8'); test('single proof wrapper always reports exit status and useful diagnostics', () => { assert.match(wrapper, /EXIT STATUS/); @@ -13,20 +15,24 @@ test('single proof wrapper always reports exit status and useful diagnostics', ( assert.match(wrapper, /node-tests\.log/); }); -test('verification prints explicit stage markers', () => { - for (const marker of ['STAGE 1\\/4: Node','STAGE 2\\/4: DPM','STAGE 3\\/4: Daml build','STAGE 4\\/4: Daml tests']) { - assert.match(verify, new RegExp(marker)); +test('shell verifier is only a compatibility exec wrapper', () => { + assert.match(shellVerify, /exec node scripts\/verify-all\.mjs/); + assert.doesNotMatch(shellVerify, /npm test/); + assert.doesNotMatch(shellVerify, /dpm build/); +}); + +test('Node verifier owns all four deterministic verification stages', () => { + for (const marker of ['STAGE 1/4: Node','STAGE 2/4: DPM','STAGE 3/4: Daml build','STAGE 4/4: Daml tests']) { + assert.match(nodeVerify, new RegExp(marker.replaceAll('/', '\\/'))); } + assert.match(nodeVerify, /spawnSync\("npm", \["test"\]/); + assert.match(nodeVerify, /spawnSync\("dpm", \["version", "--active"\]/); + assert.match(nodeVerify, /run\("STAGE 3\/4: Daml build", "dpm", \["build"\]/); + assert.match(nodeVerify, /run\("STAGE 4\/4: Daml tests", "dpm", \["test"\]/); + assert.match(nodeVerify, /BUILD\/TEST VERIFICATION COMPLETE/); }); -test('verification explicitly disables errexit and checks every stage status', () => { - assert.match(verify, /^set \+e$/m); - assert.doesNotMatch(verify, /^set\s+-[A-Za-z]*e[A-Za-z]*\s*(?:#.*)?$/m); - assert.match(verify, /npm test > evidence\/node-tests\.log 2>&1/); - assert.match(verify, /status=\$\?/); - assert.match(verify, /if \[ "\$status" -ne 0 \]; then/); - assert.match(verify, /STAGE 1\/4: Node: PASS/); - assert.match(verify, /STAGE 4\/4: Daml tests: PASS/); - assert.match(verify, /BUILD\/TEST VERIFICATION COMPLETE/); - assert.match(verify, /exit 0/); +test('real proof runner invokes the Node verifier directly', () => { + assert.match(runner, /node scripts\/verify-all\.mjs/); + assert.doesNotMatch(runner, /bash scripts\/verify-all\.sh/); }); From 5a88ed8f6c8f4f9e269c8f35bfeec40f3e476438 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:40:38 +0600 Subject: [PATCH 101/144] test: bind evidence coherence to Node verifier --- tests/evidence-coherence-static.test.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/evidence-coherence-static.test.mjs b/tests/evidence-coherence-static.test.mjs index 5f1f1d1..a95b553 100644 --- a/tests/evidence-coherence-static.test.mjs +++ b/tests/evidence-coherence-static.test.mjs @@ -3,11 +3,11 @@ import assert from 'node:assert/strict'; import fs from 'node:fs/promises'; const checker = await fs.readFile(new URL('../scripts/check-evidence.mjs', import.meta.url), 'utf8'); -const verifier = await fs.readFile(new URL('../scripts/verify-all.sh', import.meta.url), 'utf8'); +const verifier = await fs.readFile(new URL('../scripts/verify-all.mjs', import.meta.url), 'utf8'); const runner = await fs.readFile(new URL('../scripts/run-local-proof.sh', import.meta.url), 'utf8'); test('verification and Canton proof are forced onto the exact checked-out commit', () => { - assert.match(verifier, /SOURCE_COMMIT=\$\(git rev-parse HEAD\)/); + assert.match(verifier, /spawnSync\("git", \["rev-parse", "HEAD"\]/); assert.match(runner, /SOURCE_COMMIT=\$\(git rev-parse HEAD\)/); assert.match(runner, /export GITHUB_SHA="\$SOURCE_COMMIT"/); assert.match(checker, /proof\.sourceCommit!==verification\.commit/); From 4eee0c71562e781a849f17cc39e7a6c4f307936e Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:40:50 +0600 Subject: [PATCH 102/144] fix: resolve repository root correctly in Node verifier --- scripts/verify-all.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/verify-all.mjs b/scripts/verify-all.mjs index d47b7da..a0321dd 100644 --- a/scripts/verify-all.mjs +++ b/scripts/verify-all.mjs @@ -1,8 +1,9 @@ import { spawnSync } from "node:child_process"; import { mkdirSync, rmSync, writeFileSync } from "node:fs"; import { resolve } from "node:path"; +import { fileURLToPath } from "node:url"; -const root = resolve(new URL("..", import.meta.url).pathname, ".."); +const root = fileURLToPath(new URL("..", import.meta.url)); process.chdir(root); mkdirSync("evidence", { recursive: true }); rmSync("evidence/verification.json", { force: true }); From cbad67cba5c1475e9bac3e47dbc5a6a27e70ad17 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:41:21 +0600 Subject: [PATCH 103/144] fix: decode Canton 3.5 wrapped transaction responses --- src/canton-json-api.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/canton-json-api.mjs b/src/canton-json-api.mjs index 7931395..dc140cf 100644 --- a/src/canton-json-api.mjs +++ b/src/canton-json-api.mjs @@ -75,7 +75,7 @@ export function extractCreatedEvents(activeContractsResponse) { } export function extractTransactionCreatedEvents(response) { - const events = response?.transaction?.events; + const events = response?.transaction?.value?.events; if (!Array.isArray(events)) return []; return events .map(event => event?.CreatedEvent?.value || event?.CreatedEvent) From 8e7b58316aa23d6994bbed0e9aa30c81e0092c2f Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:41:24 +0600 Subject: [PATCH 104/144] fix: read transaction metadata from Canton JSON wrapper --- src/orchestrator.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/orchestrator.mjs b/src/orchestrator.mjs index 4a3651c..cb44406 100644 --- a/src/orchestrator.mjs +++ b/src/orchestrator.mjs @@ -60,7 +60,7 @@ async function submitAndFind({ commandId: `${workflowId}-${Date.now()}-${Math.random().toString(16).slice(2)}`, packageIdSelectionPreference: client.packageIdSelectionPreference || [] }); - const transaction = response?.transaction; + const transaction = response?.transaction?.value; const offset = Number(transaction?.offset); if (!transaction?.updateId || !Number.isSafeInteger(offset) || offset <= 0) { throw new Error("Canton transaction response did not return updateId and offset"); From ae374c44f2915c6cf8a4efac52ceb6f5f300854a Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:41:27 +0600 Subject: [PATCH 105/144] test: mirror Canton wrapped transaction JSON --- tests/lifecycle-transport.test.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lifecycle-transport.test.mjs b/tests/lifecycle-transport.test.mjs index de5cf50..6bc49ad 100644 --- a/tests/lifecycle-transport.test.mjs +++ b/tests/lifecycle-transport.test.mjs @@ -55,7 +55,7 @@ function installTransport(t, {unmerged=false,negativeNetworkFailure=false}={}) { seq++; const event={contractId:`unit-contract-${seq}`,templateId:`${'a'.repeat(64)}:CommitLedger:${name}`,createArgument:args}; active.set(event.contractId,event); - if(transactionMode) return Response.json({transaction:{updateId:`unit-update-${seq}`,offset:seq,events:[{CreatedEvent:{value:event}}]}}); + if(transactionMode) return Response.json({transaction:{value:{updateId:`unit-update-${seq}`,offset:seq,events:[{CreatedEvent:event}]}}}); return Response.json({updateId:`unit-update-${seq}`,completionOffset:seq}); }); return {commands,active}; From db85d00751610b25a48f6c88e73da9d1a70c9d9c Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:41:29 +0600 Subject: [PATCH 106/144] test: lock Canton 3.5 transaction wrapper shape --- tests/canton-json-api.test.mjs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/canton-json-api.test.mjs b/tests/canton-json-api.test.mjs index bf76ec4..8fada3a 100644 --- a/tests/canton-json-api.test.mjs +++ b/tests/canton-json-api.test.mjs @@ -33,15 +33,15 @@ test("extracts active created events from Canton JSON API responses", () => { test("extracts created events from submit-and-wait transaction responses", () => { const events = extractTransactionCreatedEvents({ - transaction: { + transaction: { value: { updateId: "unit-update", offset: 1, - events: [{ CreatedEvent: { value: { + events: [{ CreatedEvent: { contractId: "00created", templateId: "a".repeat(64) + ":CommitLedger:Bounty", createArgument: { bountyId: "bounty-5" } - } } }] - } + } }] + } } }); assert.equal(events.length, 1); assert.equal(events[0].contractId, "00created"); From 1ca1503b00f78c4984766c637b235df0acf0c3a1 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:42:38 +0600 Subject: [PATCH 107/144] fix: prefer official direct Canton JSON transaction shape --- src/canton-json-api.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/canton-json-api.mjs b/src/canton-json-api.mjs index dc140cf..47b4a3a 100644 --- a/src/canton-json-api.mjs +++ b/src/canton-json-api.mjs @@ -75,7 +75,8 @@ export function extractCreatedEvents(activeContractsResponse) { } export function extractTransactionCreatedEvents(response) { - const events = response?.transaction?.value?.events; + const transaction = response?.transaction?.value || response?.transaction; + const events = transaction?.events; if (!Array.isArray(events)) return []; return events .map(event => event?.CreatedEvent?.value || event?.CreatedEvent) From 4d2e484b15ef2e534159bac22c31088aa60d375b Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:42:41 +0600 Subject: [PATCH 108/144] fix: accept official Canton JSON transaction response --- src/orchestrator.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/orchestrator.mjs b/src/orchestrator.mjs index cb44406..3b0016f 100644 --- a/src/orchestrator.mjs +++ b/src/orchestrator.mjs @@ -60,7 +60,7 @@ async function submitAndFind({ commandId: `${workflowId}-${Date.now()}-${Math.random().toString(16).slice(2)}`, packageIdSelectionPreference: client.packageIdSelectionPreference || [] }); - const transaction = response?.transaction?.value; + const transaction = response?.transaction?.value || response?.transaction; const offset = Number(transaction?.offset); if (!transaction?.updateId || !Number.isSafeInteger(offset) || offset <= 0) { throw new Error("Canton transaction response did not return updateId and offset"); From ba9bddcf157673d51e322c947af39319b31cff05 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:42:44 +0600 Subject: [PATCH 109/144] test: mirror official direct Canton JSON transaction --- tests/lifecycle-transport.test.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lifecycle-transport.test.mjs b/tests/lifecycle-transport.test.mjs index 6bc49ad..d689f2e 100644 --- a/tests/lifecycle-transport.test.mjs +++ b/tests/lifecycle-transport.test.mjs @@ -55,7 +55,7 @@ function installTransport(t, {unmerged=false,negativeNetworkFailure=false}={}) { seq++; const event={contractId:`unit-contract-${seq}`,templateId:`${'a'.repeat(64)}:CommitLedger:${name}`,createArgument:args}; active.set(event.contractId,event); - if(transactionMode) return Response.json({transaction:{value:{updateId:`unit-update-${seq}`,offset:seq,events:[{CreatedEvent:event}]}}}); + if(transactionMode) return Response.json({transaction:{updateId:`unit-update-${seq}`,offset:seq,events:[{CreatedEvent:event}]}}); return Response.json({updateId:`unit-update-${seq}`,completionOffset:seq}); }); return {commands,active}; From 7af4f321aa137054857a8b2cef56f2dab97ec1b2 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:42:47 +0600 Subject: [PATCH 110/144] test: lock official Canton JSON transaction response shape --- tests/canton-json-api.test.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/canton-json-api.test.mjs b/tests/canton-json-api.test.mjs index 8fada3a..53d04fb 100644 --- a/tests/canton-json-api.test.mjs +++ b/tests/canton-json-api.test.mjs @@ -33,7 +33,7 @@ test("extracts active created events from Canton JSON API responses", () => { test("extracts created events from submit-and-wait transaction responses", () => { const events = extractTransactionCreatedEvents({ - transaction: { value: { + transaction: { updateId: "unit-update", offset: 1, events: [{ CreatedEvent: { @@ -41,7 +41,7 @@ test("extracts created events from submit-and-wait transaction responses", () => templateId: "a".repeat(64) + ":CommitLedger:Bounty", createArgument: { bountyId: "bounty-5" } } }] - } } + } }); assert.equal(events.length, 1); assert.equal(events[0].contractId, "00created"); From 76bdfa9572c236fb2b55f3a939733ebe869b6003 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:43:02 +0600 Subject: [PATCH 111/144] fix: capture final proof stage failures under shell errexit --- scripts/run-local-proof.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/run-local-proof.sh b/scripts/run-local-proof.sh index fdecda6..27751a5 100755 --- a/scripts/run-local-proof.sh +++ b/scripts/run-local-proof.sh @@ -97,8 +97,10 @@ set +a export COMMITLEDGER_EVIDENCE_FILE="$PWD/evidence/canton-proof.json" echo "=== STAGE 5/6: Real Canton lifecycle ===" +set +e npm run demo:full 2>&1 | tee evidence/canton-demo.log status=${PIPESTATUS[0]} +set -e if [ "$status" -ne 0 ]; then echo "=== STAGE 5/6: Real Canton lifecycle: FAILED with status $status ===" >&2 exit "$status" @@ -106,8 +108,10 @@ fi echo "=== STAGE 5/6: Real Canton lifecycle: PASS ===" echo "=== STAGE 6/6: Evidence validation ===" +set +e node scripts/check-evidence.mjs status=$? +set -e if [ "$status" -ne 0 ]; then echo "=== STAGE 6/6: Evidence validation: FAILED with status $status ===" >&2 exit "$status" From c94efb54c33b8756b595f499287372fd7d71e9ac Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:45:25 +0600 Subject: [PATCH 112/144] test: align verifier assertions with generic process runner --- tests/proof-wrapper-static.test.mjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/proof-wrapper-static.test.mjs b/tests/proof-wrapper-static.test.mjs index 1275de4..39ffc4e 100644 --- a/tests/proof-wrapper-static.test.mjs +++ b/tests/proof-wrapper-static.test.mjs @@ -25,7 +25,9 @@ test('Node verifier owns all four deterministic verification stages', () => { for (const marker of ['STAGE 1/4: Node','STAGE 2/4: DPM','STAGE 3/4: Daml build','STAGE 4/4: Daml tests']) { assert.match(nodeVerify, new RegExp(marker.replaceAll('/', '\\/'))); } - assert.match(nodeVerify, /spawnSync\("npm", \["test"\]/); + assert.match(nodeVerify, /function run\(label, command, args/); + assert.match(nodeVerify, /spawnSync\(command, args/); + assert.match(nodeVerify, /run\("STAGE 1\/4: Node", "npm", \["test"\]/); assert.match(nodeVerify, /spawnSync\("dpm", \["version", "--active"\]/); assert.match(nodeVerify, /run\("STAGE 3\/4: Daml build", "dpm", \["build"\]/); assert.match(nodeVerify, /run\("STAGE 4\/4: Daml tests", "dpm", \["test"\]/); From 1343c48e4e305bc9069bf6fcbd78afa79dc3f982 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:47:20 +0600 Subject: [PATCH 113/144] fix: use async verifier with process trace diagnostics --- scripts/verify-all.mjs | 155 +++++++++++++++++++++++------------------ 1 file changed, 89 insertions(+), 66 deletions(-) diff --git a/scripts/verify-all.mjs b/scripts/verify-all.mjs index a0321dd..fcb2306 100644 --- a/scripts/verify-all.mjs +++ b/scripts/verify-all.mjs @@ -1,5 +1,5 @@ -import { spawnSync } from "node:child_process"; -import { mkdirSync, rmSync, writeFileSync } from "node:fs"; +import { spawn } from "node:child_process"; +import { mkdirSync, rmSync, writeFileSync, appendFileSync } from "node:fs"; import { resolve } from "node:path"; import { fileURLToPath } from "node:url"; @@ -7,6 +7,7 @@ const root = fileURLToPath(new URL("..", import.meta.url)); process.chdir(root); mkdirSync("evidence", { recursive: true }); rmSync("evidence/verification.json", { force: true }); +rmSync("evidence/verifier-trace.log", { force: true }); const env = { ...process.env, @@ -14,76 +15,98 @@ const env = { DPM_SDK_VERSION: "3.5.12" }; -function run(label, command, args, { cwd = root, logFile } = {}) { +function trace(message) { + const line = `[${new Date().toISOString()}] ${message}\n`; + appendFileSync("evidence/verifier-trace.log", line); + process.stderr.write(line); +} + +async function run(label, command, args, { cwd = root, logFile } = {}) { process.stdout.write(`=== ${label} ===\n`); - const result = spawnSync(command, args, { - cwd, - env, - encoding: "utf8", - stdio: ["ignore", "pipe", "pipe"] + trace(`START ${label}: ${command} ${args.join(" ")} cwd=${cwd}`); + + return await new Promise((resolveRun, rejectRun) => { + const child = spawn(command, args, { + cwd, + env, + stdio: ["ignore", "pipe", "pipe"] + }); + + let output = ""; + child.stdout.on("data", chunk => { + const text = chunk.toString(); + output += text; + process.stdout.write(text); + }); + child.stderr.on("data", chunk => { + const text = chunk.toString(); + output += text; + process.stderr.write(text); + }); + child.on("error", error => { + if (logFile) writeFileSync(logFile, output); + trace(`ERROR ${label}: ${error.message}`); + rejectRun(error); + }); + child.on("close", (code, signal) => { + if (logFile) writeFileSync(logFile, output); + trace(`CLOSE ${label}: code=${code} signal=${signal || "none"}`); + if (code !== 0) { + rejectRun(new Error(`${label} failed with status ${code}${signal ? ` signal ${signal}` : ""}`)); + return; + } + process.stdout.write(`=== ${label}: PASS ===\n`); + resolveRun(); + }); }); - const output = `${result.stdout || ""}${result.stderr || ""}`; - if (logFile) writeFileSync(logFile, output); - process.stdout.write(output); - if (result.error) { - process.stderr.write(`=== ${label}: FAILED to launch: ${result.error.message} ===\n`); - process.exit(127); - } - if (result.status !== 0) { - process.stderr.write(`=== ${label}: FAILED with status ${result.status} ===\n`); - process.exit(result.status ?? 1); - } - process.stdout.write(`=== ${label}: PASS ===\n`); } -const source = spawnSync("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8" }); -if (source.status !== 0) { - process.stderr.write(source.stderr || "Unable to resolve Git HEAD\n"); - process.exit(source.status ?? 1); -} -const sourceCommit = source.stdout.trim(); +async function main() { + const source = await new Promise((resolveGit, rejectGit) => { + const child = spawn("git", ["rev-parse", "HEAD"], { cwd: root, env, stdio: ["ignore", "pipe", "pipe"] }); + let out = "", err = ""; + child.stdout.on("data", c => out += c.toString()); + child.stderr.on("data", c => err += c.toString()); + child.on("error", rejectGit); + child.on("close", code => code === 0 ? resolveGit(out.trim()) : rejectGit(new Error(err || "Unable to resolve Git HEAD"))); + }); -run("STAGE 1/4: Node", "npm", ["test"], { logFile: "evidence/node-tests.log" }); + trace(`SOURCE ${source}`); -const dpmCheck = spawnSync("dpm", ["version", "--active"], { - cwd: resolve(root, "daml"), - env, - encoding: "utf8" -}); -if (dpmCheck.error?.code === "ENOENT") { - const message = "BLOCKED: DPM is not installed. Run bash scripts/bootstrap-dpm.sh on an internet-connected development machine.\n"; - writeFileSync("evidence/daml-build.log", message); - process.stderr.write(message); - process.exit(2); -} -const dpmOutput = `${dpmCheck.stdout || ""}${dpmCheck.stderr || ""}`; -writeFileSync("evidence/dpm-version.log", dpmOutput); -process.stdout.write("=== STAGE 2/4: DPM ===\n"); -process.stdout.write(dpmOutput); -if (dpmCheck.status !== 0) { - process.stderr.write(`=== STAGE 2/4: DPM: FAILED with status ${dpmCheck.status} ===\n`); - process.exit(dpmCheck.status ?? 1); -} -process.stdout.write("=== STAGE 2/4: DPM: PASS ===\n"); + await run("STAGE 1/4: Node", "npm", ["test"], { logFile: "evidence/node-tests.log" }); -run("STAGE 3/4: Daml build", "dpm", ["build"], { - cwd: resolve(root, "daml"), - logFile: "evidence/daml-build.log" -}); -run("STAGE 4/4: Daml tests", "dpm", ["test"], { - cwd: resolve(root, "daml"), - logFile: "evidence/daml-tests.log" -}); + await run("STAGE 2/4: DPM", "dpm", ["version", "--active"], { + cwd: resolve(root, "daml"), + logFile: "evidence/dpm-version.log" + }); -writeFileSync( - "evidence/verification.json", - JSON.stringify({ - node: true, - damlBuild: true, - damlTests: true, - verifiedAt: new Date().toISOString(), - commit: sourceCommit - }, null, 2) + "\n" -); + await run("STAGE 3/4: Daml build", "dpm", ["build"], { + cwd: resolve(root, "daml"), + logFile: "evidence/daml-build.log" + }); + + await run("STAGE 4/4: Daml tests", "dpm", ["test"], { + cwd: resolve(root, "daml"), + logFile: "evidence/daml-tests.log" + }); -process.stdout.write("=== BUILD/TEST VERIFICATION COMPLETE ===\n"); + writeFileSync( + "evidence/verification.json", + JSON.stringify({ + node: true, + damlBuild: true, + damlTests: true, + verifiedAt: new Date().toISOString(), + commit: source + }, null, 2) + "\n" + ); + + trace("COMPLETE verification"); + process.stdout.write("=== BUILD/TEST VERIFICATION COMPLETE ===\n"); +} + +main().catch(error => { + trace(`FATAL ${error?.stack || error?.message || String(error)}`); + process.stderr.write(`VERIFICATION FAILED: ${error?.message || String(error)}\n`); + process.exitCode = 1; +}); From bf942d4bfec7cd6e51a75f456a46e8671a0f84a6 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:47:33 +0600 Subject: [PATCH 114/144] test: align static assertions with async verifier --- tests/proof-wrapper-static.test.mjs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/proof-wrapper-static.test.mjs b/tests/proof-wrapper-static.test.mjs index 39ffc4e..59244e0 100644 --- a/tests/proof-wrapper-static.test.mjs +++ b/tests/proof-wrapper-static.test.mjs @@ -25,12 +25,15 @@ test('Node verifier owns all four deterministic verification stages', () => { for (const marker of ['STAGE 1/4: Node','STAGE 2/4: DPM','STAGE 3/4: Daml build','STAGE 4/4: Daml tests']) { assert.match(nodeVerify, new RegExp(marker.replaceAll('/', '\\/'))); } - assert.match(nodeVerify, /function run\(label, command, args/); - assert.match(nodeVerify, /spawnSync\(command, args/); - assert.match(nodeVerify, /run\("STAGE 1\/4: Node", "npm", \["test"\]/); - assert.match(nodeVerify, /spawnSync\("dpm", \["version", "--active"\]/); - assert.match(nodeVerify, /run\("STAGE 3\/4: Daml build", "dpm", \["build"\]/); - assert.match(nodeVerify, /run\("STAGE 4\/4: Daml tests", "dpm", \["test"\]/); + assert.match(nodeVerify, /function trace\(message\)/); + assert.match(nodeVerify, /async function run\(label, command, args/); + assert.match(nodeVerify, /const child = spawn\(command, args/); + assert.match(nodeVerify, /child\.on\("close", \(code, signal\)/); + assert.match(nodeVerify, /verifier-trace\.log/); + assert.match(nodeVerify, /await run\("STAGE 1\/4: Node", "npm", \["test"\]/); + assert.match(nodeVerify, /await run\("STAGE 2\/4: DPM", "dpm", \["version", "--active"\]/); + assert.match(nodeVerify, /await run\("STAGE 3\/4: Daml build", "dpm", \["build"\]/); + assert.match(nodeVerify, /await run\("STAGE 4\/4: Daml tests", "dpm", \["test"\]/); assert.match(nodeVerify, /BUILD\/TEST VERIFICATION COMPLETE/); }); From 999df59aa38137186171390e39e817fb6ef794bf Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:47:42 +0600 Subject: [PATCH 115/144] fix: surface verifier process trace in proof diagnostics --- scripts/prove-and-report.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/prove-and-report.sh b/scripts/prove-and-report.sh index 27ad260..391155a 100644 --- a/scripts/prove-and-report.sh +++ b/scripts/prove-and-report.sh @@ -23,7 +23,7 @@ if [ "$status" -eq 0 ]; then ls -lah evidence else echo "=== FAILURE DIAGNOSTICS ===" - for file in evidence/canton-demo.log evidence/canton-sandbox.log evidence/daml-tests.log evidence/daml-build.log evidence/dpm-version.log evidence/node-tests.log + for file in evidence/verifier-trace.log evidence/canton-demo.log evidence/canton-sandbox.log evidence/daml-tests.log evidence/daml-build.log evidence/dpm-version.log evidence/node-tests.log do if [ -f "$file" ]; then echo From dd8cd7625caff2cbe3b3c8404fcffc46e736619e Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:52:31 +0600 Subject: [PATCH 116/144] test: align evidence coherence with async git resolver --- tests/evidence-coherence-static.test.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/evidence-coherence-static.test.mjs b/tests/evidence-coherence-static.test.mjs index a95b553..f012a6c 100644 --- a/tests/evidence-coherence-static.test.mjs +++ b/tests/evidence-coherence-static.test.mjs @@ -7,7 +7,8 @@ const verifier = await fs.readFile(new URL('../scripts/verify-all.mjs', import.m const runner = await fs.readFile(new URL('../scripts/run-local-proof.sh', import.meta.url), 'utf8'); test('verification and Canton proof are forced onto the exact checked-out commit', () => { - assert.match(verifier, /spawnSync\("git", \["rev-parse", "HEAD"\]/); + assert.match(verifier, /spawn\("git", \["rev-parse", "HEAD"\]/); + assert.match(verifier, /const source = await new Promise/); assert.match(runner, /SOURCE_COMMIT=\$\(git rev-parse HEAD\)/); assert.match(runner, /export GITHUB_SHA="\$SOURCE_COMMIT"/); assert.match(checker, /proof\.sourceCommit!==verification\.commit/); From db1903273586229142125bebad9be24715e35cd6 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:54:41 +0600 Subject: [PATCH 117/144] fix: bypass npm lifecycle in proof verification --- scripts/verify-all.mjs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/verify-all.mjs b/scripts/verify-all.mjs index fcb2306..410e9d9 100644 --- a/scripts/verify-all.mjs +++ b/scripts/verify-all.mjs @@ -1,5 +1,5 @@ import { spawn } from "node:child_process"; -import { mkdirSync, rmSync, writeFileSync, appendFileSync } from "node:fs"; +import { mkdirSync, rmSync, writeFileSync, appendFileSync, readdirSync } from "node:fs"; import { resolve } from "node:path"; import { fileURLToPath } from "node:url"; @@ -73,7 +73,12 @@ async function main() { trace(`SOURCE ${source}`); - await run("STAGE 1/4: Node", "npm", ["test"], { logFile: "evidence/node-tests.log" }); + const testFiles = readdirSync(resolve(root, "tests")) + .filter(name => name.endsWith(".test.mjs")) + .sort() + .map(name => resolve(root, "tests", name)); + trace(`NODE_TEST_FILES ${testFiles.length}`); + await run("STAGE 1/4: Node", process.execPath, ["--test", ...testFiles], { logFile: "evidence/node-tests.log" }); await run("STAGE 2/4: DPM", "dpm", ["version", "--active"], { cwd: resolve(root, "daml"), From ce60b522bc41d3375c5dd671bd5a2f7e73026a05 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 10:54:50 +0600 Subject: [PATCH 118/144] test: lock direct Node test execution in proof verifier --- tests/proof-wrapper-static.test.mjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/proof-wrapper-static.test.mjs b/tests/proof-wrapper-static.test.mjs index 59244e0..5fe386f 100644 --- a/tests/proof-wrapper-static.test.mjs +++ b/tests/proof-wrapper-static.test.mjs @@ -30,7 +30,9 @@ test('Node verifier owns all four deterministic verification stages', () => { assert.match(nodeVerify, /const child = spawn\(command, args/); assert.match(nodeVerify, /child\.on\("close", \(code, signal\)/); assert.match(nodeVerify, /verifier-trace\.log/); - assert.match(nodeVerify, /await run\("STAGE 1\/4: Node", "npm", \["test"\]/); + assert.match(nodeVerify, /readdirSync\(resolve\(root, "tests"\)\)/); + assert.match(nodeVerify, /NODE_TEST_FILES/); + assert.match(nodeVerify, /await run\("STAGE 1\/4: Node", process\.execPath, \["--test", \.\.\.testFiles\]/); assert.match(nodeVerify, /await run\("STAGE 2\/4: DPM", "dpm", \["version", "--active"\]/); assert.match(nodeVerify, /await run\("STAGE 3\/4: Daml build", "dpm", \["build"\]/); assert.match(nodeVerify, /await run\("STAGE 4\/4: Daml tests", "dpm", \["test"\]/); From 355ef478d9287ecb39ab3d1e25dcbfdceacf0579 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 11:33:17 +0600 Subject: [PATCH 119/144] chore: add Render proof build entrypoint --- scripts/render-proof-build.sh | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 scripts/render-proof-build.sh diff --git a/scripts/render-proof-build.sh b/scripts/render-proof-build.sh new file mode 100644 index 0000000..c892300 --- /dev/null +++ b/scripts/render-proof-build.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "$0")/.." +bash scripts/bootstrap-dpm.sh +export PATH="${DPM_HOME:-$HOME/.dpm}/bin:$PATH" +bash scripts/prove-and-report.sh From 424fd10ad8c90aaa133b5ab33f2a50f5f467ffa1 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 11:36:26 +0600 Subject: [PATCH 120/144] fix: restore pristine commit before Render proof --- scripts/render-proof-build.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/render-proof-build.sh b/scripts/render-proof-build.sh index c892300..3c3e32c 100644 --- a/scripts/render-proof-build.sh +++ b/scripts/render-proof-build.sh @@ -3,4 +3,6 @@ set -euo pipefail cd "$(dirname "$0")/.." bash scripts/bootstrap-dpm.sh export PATH="${DPM_HOME:-$HOME/.dpm}/bin:$PATH" +# Provider/bootstrap tooling may touch tracked metadata. Restore the exact checked-out commit before evidence generation. +git reset --hard HEAD bash scripts/prove-and-report.sh From 86842e1ec17b4b5ef0b4ba0756f4bfb05990b2f4 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 12:02:10 +0600 Subject: [PATCH 121/144] fix: bootstrap Temurin JRE for Render Daml tests --- scripts/render-proof-build.sh | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/scripts/render-proof-build.sh b/scripts/render-proof-build.sh index 3c3e32c..cc917eb 100644 --- a/scripts/render-proof-build.sh +++ b/scripts/render-proof-build.sh @@ -3,6 +3,19 @@ set -euo pipefail cd "$(dirname "$0")/.." bash scripts/bootstrap-dpm.sh export PATH="${DPM_HOME:-$HOME/.dpm}/bin:$PATH" + +if ! command -v java >/dev/null 2>&1; then + JAVA_ROOT="$HOME/.local/temurin-21" + mkdir -p "$JAVA_ROOT" + curl --fail --show-error --location --retry 3 "https://api.adoptium.net/v3/binary/latest/21/ga/linux/x64/jre/hotspot/normal/eclipse" -o "$JAVA_ROOT/temurin-jre.tar.gz" + tar -xzf "$JAVA_ROOT/temurin-jre.tar.gz" -C "$JAVA_ROOT" + rm -f "$JAVA_ROOT/temurin-jre.tar.gz" + JAVA_HOME=$(find "$JAVA_ROOT" -mindepth 1 -maxdepth 1 -type d | head -n 1) + export JAVA_HOME + export PATH="$JAVA_HOME/bin:$PATH" +fi + +java -version # Provider/bootstrap tooling may touch tracked metadata. Restore the exact checked-out commit before evidence generation. git reset --hard HEAD bash scripts/prove-and-report.sh From 3c45b1abe0f83d49fca6fb2c0bb9af5e83cea0f3 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 12:22:17 +0600 Subject: [PATCH 122/144] fix: extract DAR package id via damlc inspect-dar --- scripts/dar-package-id.py | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/scripts/dar-package-id.py b/scripts/dar-package-id.py index ae4f002..f0fcb8e 100755 --- a/scripts/dar-package-id.py +++ b/scripts/dar-package-id.py @@ -1,15 +1,30 @@ #!/usr/bin/env python3 -"""Read the main package hash from the actual built DAR, never from a placeholder.""" +"""Extract the main package ID from a built DAR using the Daml compiler's canonical inspect-dar output.""" +import json import re +import subprocess import sys -import zipfile -with zipfile.ZipFile(sys.argv[1]) as dar: - manifest = dar.read('META-INF/MANIFEST.MF').decode('utf-8') -manifest = re.sub(r'\r?\n ', '', manifest) -main = re.search(r'^Main-Dalf:\s*(.+)$', manifest, re.MULTILINE) -if not main: - raise SystemExit('Main-Dalf missing from DAR manifest') -package = re.search(r'([a-f0-9]{64})\.dalf$', main.group(1).strip()) -if not package: - raise SystemExit('Cannot derive main package ID from DAR manifest') -print(package.group(1)) + +if len(sys.argv) != 2: + raise SystemExit("usage: dar-package-id.py ") + +result = subprocess.run( + ["dpm", "damlc", "inspect-dar", "--json", sys.argv[1]], + check=False, + capture_output=True, + text=True, +) +if result.returncode != 0: + sys.stderr.write(result.stderr or result.stdout) + raise SystemExit(result.returncode or 1) + +try: + payload = json.loads(result.stdout) +except json.JSONDecodeError as exc: + raise SystemExit(f"inspect-dar returned invalid JSON: {exc}") from exc + +package_id = payload.get("main_package_id") +if not isinstance(package_id, str) or not re.fullmatch(r"[a-f0-9]{64}", package_id): + raise SystemExit(f"inspect-dar returned invalid main_package_id: {package_id!r}") + +print(package_id) From 19515e6834c26f5ea54222a562a83c44e3c13064 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 12:37:11 +0600 Subject: [PATCH 123/144] fix: persist DAR package id for Canton bootstrap --- scripts/bootstrap-local.mjs | 4 +++- scripts/dar-package-id.py | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) mode change 100755 => 100644 scripts/dar-package-id.py diff --git a/scripts/bootstrap-local.mjs b/scripts/bootstrap-local.mjs index 61988c6..5220316 100644 --- a/scripts/bootstrap-local.mjs +++ b/scripts/bootstrap-local.mjs @@ -1,5 +1,6 @@ // This bootstrap ONLY targets an explicitly unauthenticated loopback demo ledger. import {writeFile} from 'node:fs/promises'; +import {readFileSync} from 'node:fs'; import {setTimeout as delay} from 'node:timers/promises'; import {randomUUID} from 'node:crypto'; import {CantonJsonApi} from '../src/canton-json-api.mjs'; @@ -8,7 +9,8 @@ import {CantonApiError} from '../src/ledger-errors.mjs'; const baseUrl=process.env.CANTON_JSON_API_URL || 'http://127.0.0.1:3975'; const api=new CantonJsonApi({baseUrl,insecureLocal:true}); const suffix=randomUUID().slice(0,8); -const env={CANTON_JSON_API_URL:baseUrl,CANTON_INSECURE_LOCAL:'true',CANTON_PACKAGE_ID:process.env.CANTON_PACKAGE_ID,CANTON_PACKAGE_NAME:process.env.CANTON_PACKAGE_NAME || 'commit-ledger'}; +const fallbackPackageId=()=>readFileSync('evidence/dar-package-id.txt','utf8').trim(); +const env={CANTON_JSON_API_URL:baseUrl,CANTON_INSECURE_LOCAL:'true',CANTON_PACKAGE_ID:process.env.CANTON_PACKAGE_ID || fallbackPackageId(),CANTON_PACKAGE_NAME:process.env.CANTON_PACKAGE_NAME || 'commit-ledger'}; if(!/^[a-f0-9]{64}$/.test(env.CANTON_PACKAGE_ID||'')) throw new Error('Actual DAR package ID is required'); async function allocateParty(role) { diff --git a/scripts/dar-package-id.py b/scripts/dar-package-id.py old mode 100755 new mode 100644 index f0fcb8e..2543aac --- a/scripts/dar-package-id.py +++ b/scripts/dar-package-id.py @@ -4,6 +4,7 @@ import re import subprocess import sys +from pathlib import Path if len(sys.argv) != 2: raise SystemExit("usage: dar-package-id.py ") @@ -27,4 +28,6 @@ if not isinstance(package_id, str) or not re.fullmatch(r"[a-f0-9]{64}", package_id): raise SystemExit(f"inspect-dar returned invalid main_package_id: {package_id!r}") +Path('evidence').mkdir(exist_ok=True) +Path('evidence/dar-package-id.txt').write_text(package_id + '\n', encoding='utf-8') print(package_id) From 8ff8e111a75b7218916e3fd8384430741603a359 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 13:13:37 +0600 Subject: [PATCH 124/144] fix: use one validated source for Canton package id --- scripts/bootstrap-local.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/bootstrap-local.mjs b/scripts/bootstrap-local.mjs index 5220316..003ccc1 100644 --- a/scripts/bootstrap-local.mjs +++ b/scripts/bootstrap-local.mjs @@ -9,8 +9,8 @@ import {CantonApiError} from '../src/ledger-errors.mjs'; const baseUrl=process.env.CANTON_JSON_API_URL || 'http://127.0.0.1:3975'; const api=new CantonJsonApi({baseUrl,insecureLocal:true}); const suffix=randomUUID().slice(0,8); -const fallbackPackageId=()=>readFileSync('evidence/dar-package-id.txt','utf8').trim(); -const env={CANTON_JSON_API_URL:baseUrl,CANTON_INSECURE_LOCAL:'true',CANTON_PACKAGE_ID:process.env.CANTON_PACKAGE_ID || fallbackPackageId(),CANTON_PACKAGE_NAME:process.env.CANTON_PACKAGE_NAME || 'commit-ledger'}; +const packageId=readFileSync('evidence/dar-package-id.txt','utf8').trim(); +const env={CANTON_JSON_API_URL:baseUrl,CANTON_INSECURE_LOCAL:'true',CANTON_PACKAGE_ID:packageId,CANTON_PACKAGE_NAME:process.env.CANTON_PACKAGE_NAME || 'commit-ledger'}; if(!/^[a-f0-9]{64}$/.test(env.CANTON_PACKAGE_ID||'')) throw new Error('Actual DAR package ID is required'); async function allocateParty(role) { From 2d5a5903a3e0705529aad0b14c66767bbec60af9 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 13:13:41 +0600 Subject: [PATCH 125/144] fix: remove duplicate package id env propagation --- scripts/run-local-proof.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/run-local-proof.sh b/scripts/run-local-proof.sh index 27751a5..84b644f 100755 --- a/scripts/run-local-proof.sh +++ b/scripts/run-local-proof.sh @@ -37,8 +37,7 @@ node scripts/verify-all.mjs DAR="$PWD/daml/.daml/dist/commit-ledger-0.1.0.dar" test -f "$DAR" -export CANTON_PACKAGE_ID -CANTON_PACKAGE_ID=$(python3 scripts/dar-package-id.py "$DAR") +python3 scripts/dar-package-id.py "$DAR" >/dev/null export CANTON_PACKAGE_NAME CANTON_PACKAGE_NAME=$(sed -n 's/^name:[[:space:]]*//p' daml/daml.yaml | head -n 1 | tr -d '\r') if [ -z "$CANTON_PACKAGE_NAME" ]; then From f83b1adfc0c363dab0e00868e4e27627ebaca90d Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 13:47:10 +0600 Subject: [PATCH 126/144] fix: normalize Canton 3.5 unhandled exception envelope --- src/ledger-errors.mjs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ledger-errors.mjs b/src/ledger-errors.mjs index bff4d11..9f7a1dc 100644 --- a/src/ledger-errors.mjs +++ b/src/ledger-errors.mjs @@ -1,8 +1,11 @@ /** Fail closed: connectivity, authentication and unknown errors are not security proof. */ export class CantonApiError extends Error { constructor(status, body) { - const code = typeof body?.code === "string" ? body.code : "UNKNOWN_CANTON_ERROR"; + const rawCode = typeof body?.code === "string" ? body.code : "UNKNOWN_CANTON_ERROR"; const cause = typeof body?.cause === "string" ? body.cause : "Unrecognized Canton error response"; + const code = rawCode === "DAML_FAILURE" && cause.includes("UNHANDLED_EXCEPTION/") + ? "DAML_UNHANDLED_EXCEPTION" + : rawCode; super(`Canton JSON Ledger API failed: ${status} ${code}: ${cause}`); this.name = "CantonApiError"; this.status = status; From 2a0299578451cff7b95ecb1bf5901795767e8aaa Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 14:17:25 +0600 Subject: [PATCH 127/144] fix: support trusted Render public host binding --- src/server.mjs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/server.mjs b/src/server.mjs index 9c28350..07c4438 100644 --- a/src/server.mjs +++ b/src/server.mjs @@ -23,9 +23,13 @@ async function readJson(req) { export function createAppServer(env=process.env) { return http.createServer(async(req,res)=>{ try { - const origin=`http://${req.headers.host||'localhost'}`; + const forwardedProto=String(req.headers['x-forwarded-proto']||'').split(',')[0].trim().toLowerCase(); + const protocol=['http','https'].includes(forwardedProto)?forwardedProto:'http'; + const origin=`${protocol}://${req.headers.host||'localhost'}`; const url=new URL(req.url,origin); - if(!['127.0.0.1','localhost','[::1]'].includes(url.hostname)) return json(res,403,{error:'Loopback host required'}); + const allowedHosts=new Set(['127.0.0.1','localhost','[::1]']); + if(env.COMMITLEDGER_PUBLIC_HOST) allowedHosts.add(String(env.COMMITLEDGER_PUBLIC_HOST).trim().toLowerCase()); + if(!allowedHosts.has(url.hostname.toLowerCase())) return json(res,403,{error:'Untrusted host'}); if(req.method==='POST') { if(req.headers.origin && req.headers.origin!==origin) return json(res,403,{error:'Cross-origin write rejected'}); if(!/^application\/json(?:;|$)/i.test(req.headers['content-type']||'')) return json(res,415,{error:'application/json is required'}); @@ -85,5 +89,6 @@ export function createAppServer(env=process.env) { } if(process.argv[1] && resolve(process.argv[1])===fileURLToPath(import.meta.url)) { const port=Number(process.env.PORT||4173); - createAppServer().listen(port,'127.0.0.1',()=>console.log(`CommitLedger running at http://127.0.0.1:${port}`)); + const host=process.env.COMMITLEDGER_BIND_HOST||'127.0.0.1'; + createAppServer().listen(port,host,()=>console.log(`CommitLedger running at http://${host}:${port}`)); } From aa7117a6be0ee0637a9a1be0b1ac440435650637 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 14:17:28 +0600 Subject: [PATCH 128/144] test: cover Render forwarded host and origin --- tests/server.test.mjs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/server.test.mjs b/tests/server.test.mjs index 6fe4a96..c8fea82 100644 --- a/tests/server.test.mjs +++ b/tests/server.test.mjs @@ -26,3 +26,21 @@ test('cross-origin writes and simple-form content types are rejected',async t=>{ const b=await fetch(base+'/api/demo/run',{method:'POST',headers:{'Content-Type':'text/plain'},body:'{}'}); assert.equal(b.status,415); }); + +test('configured public host accepts Render-style forwarded HTTPS origin',async t=>{ + const server=createAppServer({COMMITLEDGER_PUBLIC_HOST:'commitledger-proof-final.onrender.com'}); + await new Promise(resolve=>server.listen(0,'127.0.0.1',resolve)); + t.after(()=>new Promise(resolve=>{server.close(resolve);server.closeAllConnections();})); + const port=server.address().port; + const response=await fetch(`http://127.0.0.1:${port}/api/demo/run`,{ + method:'POST', + headers:{ + Host:'commitledger-proof-final.onrender.com', + 'X-Forwarded-Proto':'https', + Origin:'https://commitledger-proof-final.onrender.com', + 'Content-Type':'application/json' + }, + body:'{}' + }); + assert.equal(response.status,503); +}); From 722df9f69458cba0080d687105b4a0fb00f5c4d6 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 14:44:24 +0600 Subject: [PATCH 129/144] fix: test Render forwarded host with raw HTTP --- tests/server.test.mjs | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/tests/server.test.mjs b/tests/server.test.mjs index c8fea82..4df9e7d 100644 --- a/tests/server.test.mjs +++ b/tests/server.test.mjs @@ -1,5 +1,6 @@ import test from 'node:test'; import assert from 'node:assert/strict'; +import {request as httpRequest} from 'node:http'; import {createAppServer} from '../src/server.mjs'; async function withServer(t) { const server=createAppServer({}); @@ -32,15 +33,24 @@ test('configured public host accepts Render-style forwarded HTTPS origin',async await new Promise(resolve=>server.listen(0,'127.0.0.1',resolve)); t.after(()=>new Promise(resolve=>{server.close(resolve);server.closeAllConnections();})); const port=server.address().port; - const response=await fetch(`http://127.0.0.1:${port}/api/demo/run`,{ - method:'POST', - headers:{ - Host:'commitledger-proof-final.onrender.com', - 'X-Forwarded-Proto':'https', - Origin:'https://commitledger-proof-final.onrender.com', - 'Content-Type':'application/json' - }, - body:'{}' + const result=await new Promise((resolve,reject)=>{ + const req=httpRequest({ + host:'127.0.0.1', + port, + path:'/api/demo/run', + method:'POST', + headers:{ + Host:'commitledger-proof-final.onrender.com', + 'X-Forwarded-Proto':'https', + Origin:'https://commitledger-proof-final.onrender.com', + 'Content-Type':'application/json' + } + },res=>{ + res.resume(); + res.on('end',()=>resolve(res.statusCode)); + }); + req.on('error',reject); + req.end('{}'); }); - assert.equal(response.status,503); + assert.equal(result,503); }); From 87fc14847ed19aaaa3e4ded1954c731bccd70914 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 16:55:12 +0600 Subject: [PATCH 130/144] docs: record verified Render proof and submission handoff --- docs/DELIVERY_STATUS.md | 188 +++++++++++++++++++++++----------------- 1 file changed, 110 insertions(+), 78 deletions(-) diff --git a/docs/DELIVERY_STATUS.md b/docs/DELIVERY_STATUS.md index 4b3904f..c89405c 100644 --- a/docs/DELIVERY_STATUS.md +++ b/docs/DELIVERY_STATUS.md @@ -4,81 +4,113 @@ Delivery branch: `fix/verified-delivery-20260929`. ## Current candidate -- Candidate head after synchronizing `main`: `51e4ddd95a442fd11a8df80a867361db9b86c080`. -- Branch relation: **68 commits ahead, 0 behind** `main`. -- PR #8 remains **draft** and mergeable. -- The synchronization incorporated `AGENTS.md` and `docs/PROJECT_MODE.md` from `main` with a real two-parent merge commit; no force push was used. - -## Verified evidence - -- Original Node baseline: **16/16 passed**. -- Expanded delivery suite: **65/65 passed** on commit `202693cbc10d835166fe3bf6240fa56e529ff1cd`. -- A later Codespaces execution reported **74/74 Node tests passed**, DPM 3.5.12 executed, and the Daml DAR build succeeded before the final runtime hardening sweep. -- Competition-readiness evaluator: **4/4 passed** in its isolated test-first fixture. -- Judge-UI static acceptance tests: **3/3 passed** in their isolated test-first fixture. -- Official HackCanton Season 3 Rules, Timeline, Tracks and Materials were rechecked on 2026-09-29. - -These historical results are strong implementation evidence but do **not** prove the current candidate head. Final proof must be generated from one run against the exact candidate source commit. - -## Implemented - -### Verification and security -- Repository-qualified issue references, canonical PR/merge-commit verification and target-branch reachability. -- Merge commit SHA propagated through MergeEvidence and SettlementReceipt. -- Structured Canton rejection classification: network, authentication, timeout, throttling and unknown infrastructure errors cannot be promoted into security proof. -- Unique demo identities, unambiguous active-contract lookup and GitHub preflight before ledger writes. -- Server-side origin/content-type protection and no arbitrary browser command submission using server-held ledger credentials. -- Evidence provenance is bound to source commit and run identity; mixed evidence is rejected. - -### Competition readiness -- Fail-closed `PASS / BLOCKED / FAIL` readiness engine. -- External/account/publication gates remain BLOCKED until actually verified. -- Track 1 business brief, pilot plan, pitch, AI disclosure and hackathon-period work disclosure are present. -- Submission draft, judge runbook and final checklist are aligned to the competition design. - -### Judge experience -- Judge UI exposes Track 1 positioning, competition readiness and all six judging criteria. -- Runtime-sensitive states never display invented Canton transaction IDs or simulated success as real proof. -- Desktop/mobile judge-flow design and <=5-minute evidence-led demo script are prepared. - -## Current blockers - -### Real Canton proof -The only connected development device, `DESKTOP-KTDVO5M`, is currently **offline**. Because there is no active execution machine, the following cannot truthfully be produced in this session: - -- clean full-suite execution on the exact current candidate; -- DPM/Daml build and Daml Script test logs on the exact current candidate; -- DAR deployment to a live local Canton runtime; -- real Issue -> Bounty -> Claim -> PR -> Verify -> Settle -> Receipt lifecycle; -- exact wrong-evidence, unauthorized-settlement and duplicate/replay rejections; -- coherent final evidence bundle from the same run and source commit; -- browser/mobile QA against the running app; -- final evidence-backed video. - -When an execution machine is available, the intended operator command is: - -```bash -bash scripts/prove-and-report.sh -``` - -A successful run must be retained under `evidence/` and validated before this status changes to ready. - -### GitHub Actions -The latest observed GitHub Actions runs failed before recorded job steps began (`steps: []`). That is not evidence of a test assertion failure. The exact platform/account-level cause is not exposed by the current connector, so no billing, policy, runner or code cause is asserted without evidence. - -A new push has been created on 2026-09-30 after syncing `main`; CI for the new candidate must be evaluated when GitHub exposes the run. - -### External competition gates -Still intentionally unverified: -- public repository/materials accessible to a logged-out judge; -- AppsFactory 1,000 Mana; -- minimum platform activity requirement; -- project profile and journal; -- team/account eligibility; -- final portal submission and retained receipt. - -## Release decision - -CommitLedger is **implementation-advanced but not yet runtime-verified or submission-ready**. - -Keep PR #8 draft. Do not merge, close issue #4, claim 100%, or submit until the exact candidate produces real Canton proof and the external competition gates are verified. +- Exact current head: `722df9f69458cba0080d687105b4a0fb00f5c4d6`. +- PR #8 is still open and draft. Keep it draft until submission-side gates below are completed. +- Canonical Render service: `commitledger-proof-final`. +- Canonical public service URL: https://commitledger-proof-final.onrender.com +- Render deploy `dep-daucnqff3r2c73ep69u0` for the exact head above is **live**. +- The older Render service `commitledger-proof` is a discarded/dummy duplicate and is not evidence of final delivery status. + +## Verified technical proof + +The exact current candidate has completed the provider-side proof path on Render. + +Verified from the successful Render build/deploy: + +- Node suite passes: **86/86**. +- DPM **3.5.12** installs/runs. +- Daml build passes and a fresh DAR is created. +- Daml Script tests pass, including happy path and rejection/replay cases. +- Java 21 runtime is bootstrapped for Daml Script execution. +- A fresh local Canton sandbox starts and reports ready. +- The DAR package ID is derived canonically from the built DAR using `dpm damlc inspect-dar --json`. +- Three distinct local demo parties are allocated. +- Real GitHub evidence fixture used by the proof is: + - open issue: `Saidur-droid/MergeEarn#69` + - merged PR: `Saidur-droid/MergeEarn#73` + - contributor: `Saidur-droid` + - target branch: `main` +- The full Issue -> Bounty -> Claim -> PR -> Verify -> Settle -> SettlementReceipt lifecycle completes on Canton. +- Wrong-evidence rejection is captured. +- Unauthorized settlement rejection is captured. +- Duplicate/replay settlement rejection is captured as a structured Canton `CONTRACT_NOT_FOUND` rejection. +- The proof contains a real settlement receipt using clearly labelled non-production `DEMO_CREDIT`. +- Verification and Canton proof are bound to one exact source commit. +- Render build completes successfully. +- The web service binds to Render correctly and the final deploy is live. + +This closes the technical runtime blocker that previously depended on a desktop/Codespace/GitHub Actions runner. + +## Important architecture notes + +- The proof backend is Render provider-side execution, not GitHub Actions and not the user's desktop. +- Render is authorized directly against the private CommitLedger repository; no other project repository is used as a runner or mirror. +- The proof uses a local ephemeral Canton sandbox during the Render build. The public web service is the judge-facing web service; it does not claim a persistent production Canton deployment. +- `DEMO_CREDIT` is test value only. No fiat, Canton Coin, MainNet, custody, revenue, customer traction, or organizer endorsement is claimed. +- The old duplicate Render service `commitledger-proof` should be ignored and manually removed later if desired. The available Render connector cannot delete/suspend it. + +## Remaining work — do this next + +The coding/runtime-debugging phase is effectively complete. Do not reopen architecture work unless a new concrete regression appears. + +### 1. Evidence/status cleanup +- Update PR #8 body so it no longer says the real Canton proof is missing. +- Keep the exact successful commit/deploy IDs in the PR and submission notes. +- Confirm no new code changes have invalidated the proof commit before recording the final video. If code changes after `722df9f...`, rerun the full proof on the new exact head. + +### 2. Judge browser QA +- Test the canonical Render URL on desktop. +- Test the same judge flow on a narrow/mobile viewport. +- Confirm the page loads without credentials. +- Confirm IDs/evidence are legible and no secret/private data is exposed. +- Test all final links in a logged-out/private browser window. + +### 3. Final <=5-minute demo video +Record only after browser QA passes. The video should show: +1. problem + three roles; +2. real GitHub issue/PR evidence; +3. verified Canton six-step lifecycle; +4. wrong-evidence / unauthorized / replay rejection; +5. SettlementReceipt and `DEMO_CREDIT` disclaimer; +6. why Canton is necessary; +7. Track 1 business/pilot angle. + +Review the full exported video before publishing. + +### 4. Public/submission materials +Before submission: +- make the repository accessible to a logged-out judge when ready; +- publish/verify the demo video; +- verify pitch/project page; +- populate final repository/demo/video/pitch/evidence links in `docs/SUBMISSION_DRAFT.md`; +- verify every URL from a private/logged-out browser. + +### 5. AppsFactory / competition account gates +Still require human/account verification: +- required 1,000 Mana; +- minimum required platform activity; +- project profile complete; +- journal non-empty; +- team/eligibility valid; +- pre-existing code disclosure accurate; +- hackathon-period work identifiable. + +### 6. Final submission +- Review the final submission wording against actual evidence only. +- Submit through the competition portal. +- Retain the submission/portal receipt. +- Only after all mandatory gates pass should PR #8 be marked ready/merged and the project be called submission-ready. + +## Resume instruction for the next session + +Start here, in this order: + +1. Read this file and `docs/FINAL_CHECKLIST.md`. +2. Confirm Render service `commitledger-proof-final` is still live on the current exact Git head. +3. Do **not** repeat the old desktop/GitHub Actions debugging unless Render is unusable. +4. Complete browser QA. +5. Record/review the <=5-minute video. +6. Complete public links and AppsFactory gates. +7. Finish portal submission and retain receipt. + +Do not ask the owner to restate the technical history; this document is the handoff. From 0b67ff2a9e157f0b2e396f18cec26860e88a554b Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 16:55:15 +0600 Subject: [PATCH 131/144] docs: mark proven technical gates and remaining submission work --- docs/FINAL_CHECKLIST.md | 45 ++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/docs/FINAL_CHECKLIST.md b/docs/FINAL_CHECKLIST.md index e6c6138..1f78e07 100644 --- a/docs/FINAL_CHECKLIST.md +++ b/docs/FINAL_CHECKLIST.md @@ -7,30 +7,38 @@ A source-code check is not runtime proof. A runtime proof is not an account/port - [x] Merge commit SHA / branch reachability checks implemented and Node-tested. - [x] Infrastructure errors excluded from security-rejection proof. - [x] Competition-readiness PASS/BLOCKED/FAIL model implemented. -- [ ] Full current Node suite passes on final candidate commit. -- [ ] DPM 3.5.12 installs in the proof environment. -- [ ] Daml build passes and log is retained. -- [ ] Daml Script tests pass and log is retained. -- [ ] Current DAR is deployed to a real Canton runtime. -- [ ] Real Issue → Bounty → Claim → PR → Verify → Settle → Receipt flow succeeds. -- [ ] Wrong-evidence rejection is captured from Canton/Daml. -- [ ] Unauthorized settlement rejection is captured from Canton/Daml. -- [ ] Duplicate/replay settlement rejection is captured from Canton/Daml. -- [ ] Evidence bundle is coherent: one run ID + one source commit. +- [x] Full current Node suite passes on the proven candidate commit `722df9f69458cba0080d687105b4a0fb00f5c4d6` (**86/86**). +- [x] DPM 3.5.12 installs in the Render proof environment. +- [x] Daml build passes in the Render proof run. +- [x] Daml Script tests pass in the Render proof run. +- [x] Current DAR is deployed to a fresh real local Canton sandbox during the proof run. +- [x] Real Issue → Bounty → Claim → PR → Verify → Settle → Receipt flow succeeds. +- [x] Wrong-evidence rejection is captured from Canton/Daml. +- [x] Unauthorized settlement rejection is captured from Canton/Daml. +- [x] Duplicate/replay settlement rejection is captured from Canton/Daml. +- [x] Evidence bundle is coherent: one source commit and one proof run. - [ ] Desktop/mobile judge flow passes browser QA. -- [ ] No secrets appear in tracked files, logs, screenshots, or video. +- [ ] No secrets appear in final tracked/public files, screenshots, published evidence, or video. **Final publication review still required.** - [x] Track 1 business brief exists. - [x] Pilot plan exists. - [x] AI disclosure exists. - [x] Hackathon work disclosure exists. - [ ] Final ≤5-minute demo video is recorded and reviewed. +### Proven deployment +- Canonical service: `commitledger-proof-final` +- URL: https://commitledger-proof-final.onrender.com +- Proven source commit: `722df9f69458cba0080d687105b4a0fb00f5c4d6` +- Successful Render deploy: `dep-daucnqff3r2c73ep69u0` +- Status verified: **live** +- Ignore the older duplicate service `commitledger-proof`; it is not the canonical delivery. + ## Official competition gates - [x] Target track is Track 1 — RWA & Business Workflows. -- [x] Meaningful Canton integration is an explicit hard requirement in the design. +- [x] Meaningful Canton integration is an explicit hard requirement in the design and is now runtime-proven on the candidate above. - [x] Submission deadline recorded as 2026-10-09 23:59 UTC. - [ ] Public repository available to a logged-out judge. **Deferred by owner until final phase.** -- [ ] Public demo/video/pitch/project page accessible without requesting access. **Deferred until final phase.** +- [ ] Public demo/video/pitch/project page accessible without requesting access. **Final logged-out verification pending.** - [ ] Submitting account has burned/accrued the required 1,000 Mana. - [ ] At least 10 days of required platform activity is satisfied. - [ ] Project profile is complete. @@ -40,4 +48,13 @@ A source-code check is not runtime proof. A runtime proof is not an account/port - [ ] All final links tested in a private/logged-out browser window. - [ ] Participant reviews final submission and retains portal receipt. -Do not call CommitLedger submission-ready while any mandatory official gate above is unchecked. +## Next session order +1. Browser QA (desktop + mobile). +2. Final publication/secrets review. +3. Record and review the ≤5-minute video. +4. Make repository/materials judge-accessible at the chosen final moment. +5. Complete AppsFactory account/profile/journal/Mana/activity/eligibility gates. +6. Populate and test every final submission link. +7. Submit and retain the receipt. + +Do not call CommitLedger fully submission-ready while any mandatory official gate above is unchecked. From 604718da638bd86a46078061a37108a2a60794e3 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 16:55:17 +0600 Subject: [PATCH 132/144] docs: add no-repeat next-session handoff --- docs/NEXT_SESSION_HANDOFF.md | 88 ++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 docs/NEXT_SESSION_HANDOFF.md diff --git a/docs/NEXT_SESSION_HANDOFF.md b/docs/NEXT_SESSION_HANDOFF.md new file mode 100644 index 0000000..a4cce2c --- /dev/null +++ b/docs/NEXT_SESSION_HANDOFF.md @@ -0,0 +1,88 @@ +# NEXT SESSION HANDOFF — CommitLedger + +**Read this first. Do not ask the owner to repeat the project history.** + +## Where we stopped + +Technical runtime work is green on the exact commit: + +`722df9f69458cba0080d687105b4a0fb00f5c4d6` + +Canonical Render service: + +`commitledger-proof-final` + +Public URL: + +https://commitledger-proof-final.onrender.com + +Successful Render deploy: + +`dep-daucnqff3r2c73ep69u0` + +Deploy status: **live**. + +The similarly named `commitledger-proof` service is an older dummy/duplicate service. Ignore its failed status. It is not the canonical proof service. + +## What was actually proven + +- Node tests: 86/86 pass. +- DPM 3.5.12 works. +- Daml build passes. +- Daml Script tests pass. +- Temurin Java 21 is available in the proof environment. +- Fresh Canton sandbox starts and becomes ready. +- Built DAR package identity is derived from the DAR itself. +- Three distinct demo parties are allocated. +- Public real GitHub fixture passes preflight: MergeEarn issue #69 (open) + PR #73 (merged, references #69). +- Six-step Canton lifecycle completes through SettlementReceipt. +- Wrong-evidence rejection is captured. +- Unauthorized settlement rejection is captured. +- Duplicate/replay rejection is captured. +- Evidence is source-commit bound. +- Render build succeeds. +- Judge-facing web service deploys live on Render. + +## Root causes already solved — do not re-debug these without new evidence + +- GitHub hosted Actions jobs failing before steps started. +- Offline desktop execution dependency. +- missing Java in Render build image. +- brittle DAR package-ID extraction. +- duplicate package-ID env/file propagation. +- invalid closed GitHub issue fixture. +- Canton 3.5 error-envelope normalization. +- server binding to 127.0.0.1 instead of 0.0.0.0 on Render. +- forwarded HTTPS/public-host handling. +- brittle public-host regression test. + +## What remains + +This is now a **submission preparation** project, not a runtime debugging project. + +Remaining mandatory work: +- desktop browser QA; +- mobile/narrow viewport QA; +- final secrets/publication review; +- final <=5-minute video; +- public repo access for judges at the final chosen time; +- public video/pitch/project/evidence links; +- logged-out/private-window verification of all links; +- AppsFactory Mana/activity/profile/journal/team/eligibility gates; +- final pre-existing-work/hackathon-period disclosure review; +- portal submission; +- retain final submission receipt. + +## Exact next action + +Next time, begin with **browser QA of https://commitledger-proof-final.onrender.com**. + +If browser QA passes, move directly to the final video and submission package. + +Do not change production/proof code unless QA finds a concrete defect. Any code change after the proven commit invalidates the exact-commit proof and requires a fresh proof run. + +See: +- `docs/DELIVERY_STATUS.md` +- `docs/FINAL_CHECKLIST.md` +- `docs/SUBMISSION_DRAFT.md` +- `docs/DEMO_SCRIPT.md` From 26bd992787401f6458f6685d2ab76aacd05eab4e Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 30 Sep 2026 22:51:19 +0600 Subject: [PATCH 133/144] fix: surface verified deployment proof in judge UI --- src/server.mjs | 31 ++++++++++++++++++++++-- tests/judge-ui-static.test.mjs | 14 ++++++----- tests/server.test.mjs | 4 ++++ web/app.js | 44 ++++++++++++++++++++++++---------- web/index.html | 28 +++++++++++----------- web/styles.css | 3 +-- 6 files changed, 88 insertions(+), 36 deletions(-) diff --git a/src/server.mjs b/src/server.mjs index 07c4438..dd97f38 100644 --- a/src/server.mjs +++ b/src/server.mjs @@ -7,7 +7,9 @@ import {validateBountyDraft} from './domain.mjs'; import * as builders from './canton-commands.mjs'; import {CantonJsonApi} from './canton-json-api.mjs'; import {runtimeConfigFromEnv,runFullLifecycle} from './orchestrator.mjs'; -const root=fileURLToPath(new URL('../web/',import.meta.url)); +const projectRoot=fileURLToPath(new URL('../',import.meta.url)); +const root=resolve(projectRoot,'web'); +const evidenceRoot=resolve(projectRoot,'evidence'); const actions=Object.freeze({createBounty:'buildCreateBountyCommand',claimRequest:'buildClaimRequestCommand',acceptClaim:'buildAcceptClaimCommand',submitPullRequest:'buildSubmitPullRequestCommand',returnForRevision:'buildReturnForRevisionCommand',verifyMerged:'buildVerifyMergedCommand',settle:'buildSettleCommand'}); function json(res,status,payload) { res.writeHead(status,{'Content-Type':'application/json; charset=utf-8','Cache-Control':'no-store','X-Content-Type-Options':'nosniff'}); @@ -20,6 +22,21 @@ async function readJson(req) { if(!value || typeof value!=='object' || Array.isArray(value)) throw new Error('invalid JSON object'); return value; } +async function readVerifiedProof() { + const [proofRaw,verificationRaw]=await Promise.all([ + readFile(resolve(evidenceRoot,'canton-proof.json'),'utf8'), + readFile(resolve(evidenceRoot,'verification.json'),'utf8') + ]); + const proof=JSON.parse(proofRaw); + const verification=JSON.parse(verificationRaw); + if(!/^[a-f0-9]{40}$/.test(proof.sourceCommit||'') || proof.sourceCommit!==verification.commit) { + throw new Error('deployed proof artifacts are not source-commit coherent'); + } + if(proof.steps?.length!==6 || proof.negativeChecks?.length!==3 || !proof.settlementReceipt) { + throw new Error('deployed proof artifacts are incomplete'); + } + return {proof,verification}; +} export function createAppServer(env=process.env) { return http.createServer(async(req,res)=>{ try { @@ -34,6 +51,14 @@ export function createAppServer(env=process.env) { if(req.headers.origin && req.headers.origin!==origin) return json(res,403,{error:'Cross-origin write rejected'}); if(!/^application\/json(?:;|$)/i.test(req.headers['content-type']||'')) return json(res,415,{error:'application/json is required'}); } + if(req.method==='GET' && url.pathname==='/api/proof') { + try { + const {proof,verification}=await readVerifiedProof(); + return json(res,200,{ok:true,proof,verification}); + } catch { + return json(res,503,{error:'Verified deployment proof is not available'}); + } + } if(req.method==='GET' && url.pathname==='/api/health') { let runtime;let configurationError='';let ledgerReachable=false; try {runtime=runtimeConfigFromEnv(env);} catch(error) {configurationError=error.message;} @@ -44,7 +69,9 @@ export function createAppServer(env=process.env) { ledgerReachable=true; } catch { /* Connectivity is separate from configuration and ledger proof. */ } } - return json(res,200,{ok:true,service:'CommitLedger',lifecycleConfigured:Boolean(runtime),cantonConfigured:Boolean(runtime),ledgerReachable,configurationError,githubAuthenticated:Boolean(env.GITHUB_TOKEN),packageConfigured:Boolean(env.CANTON_PACKAGE_ID)}); + let verifiedProofAvailable=false; + try {await readVerifiedProof();verifiedProofAvailable=true;} catch { /* Build proof is optional outside verified deployments. */ } + return json(res,200,{ok:true,service:'CommitLedger',lifecycleConfigured:Boolean(runtime),cantonConfigured:Boolean(runtime),ledgerReachable,verifiedProofAvailable,configurationError,githubAuthenticated:Boolean(env.GITHUB_TOKEN),packageConfigured:Boolean(env.CANTON_PACKAGE_ID)}); } if(req.method==='POST' && url.pathname==='/api/canton/submit-command') { return json(res,403,{error:'Arbitrary browser-supplied ledger commands are disabled; use the verified lifecycle'}); diff --git a/tests/judge-ui-static.test.mjs b/tests/judge-ui-static.test.mjs index 344b78b..f4a5c2f 100644 --- a/tests/judge-ui-static.test.mjs +++ b/tests/judge-ui-static.test.mjs @@ -4,9 +4,9 @@ import fs from 'node:fs/promises'; const html = await fs.readFile(new URL('../web/index.html', import.meta.url), 'utf8'); -test('judge UI exposes competition readiness without claiming ready by default', () => { +test('judge UI exposes competition readiness without claiming submission ready', () => { assert.match(html, /id="competition-readiness"/); - assert.match(html, /BLOCKED/); + assert.match(html, /BLOCKED · external gates pending/); assert.doesNotMatch(html, /SUBMISSION READY/); }); @@ -16,14 +16,16 @@ test('judge UI maps all six official judging criteria', () => { } }); -test('judge UI identifies Track 1 and keeps runtime proof evidence-first', () => { +test('judge UI identifies Track 1 and describes proof as evidence-first', () => { assert.match(html, /Track 1/); - assert.match(html, /No sample transaction IDs or simulated settlement success/); + assert.match(html, /no sample transaction IDs or simulated settlement success/i); }); -test('judge cockpit exposes source authorization settlement and runtime summary', () => { +test('judge cockpit exposes current verified fixture and runtime proof state', () => { for (const label of ['Source','Authorization','Settlement','Runtime']) { assert.match(html, new RegExp(label)); } - assert.match(html, /Runtime[\s\S]*BLOCKED/); + assert.match(html, /GitHub #69 → PR #73/); + assert.match(html, /Runtime[\s\S]*VERIFIED/); + assert.match(html, /PASS[\s\S]*Daml \+ real Canton proof/); }); diff --git a/tests/server.test.mjs b/tests/server.test.mjs index 4df9e7d..3b0cafb 100644 --- a/tests/server.test.mjs +++ b/tests/server.test.mjs @@ -12,6 +12,10 @@ test('health never calls unconfigured Canton ready',async t=>{ const base=await withServer(t);const value=await (await fetch(base+'/api/health')).json(); assert.equal(value.lifecycleConfigured,false);assert.equal(value.ledgerReachable,false); }); +test('verified proof endpoint fails closed when build artifacts are absent',async t=>{ + const base=await withServer(t);const response=await fetch(base+'/api/proof'); + assert.equal(response.status,503); +}); test('unconfigured demo fails closed',async t=>{ const base=await withServer(t);const response=await fetch(base+'/api/demo/run',{method:'POST',headers:{'Content-Type':'application/json'},body:'{}'}); assert.equal(response.status,503); diff --git a/web/app.js b/web/app.js index 6e35036..609f78e 100644 --- a/web/app.js +++ b/web/app.js @@ -7,27 +7,46 @@ async function request(path,body) { if(!response.ok) throw new Error(value.error||'Request failed'); return value; } -async function health() { - try { - const h=await request('/api/health');ready=h.lifecycleConfigured && h.ledgerReachable; - $('#canton-badge').textContent=ready?'Canton connected':'Canton setup pending'; - $('#live-status').textContent=ready?'Ready for a real ledger run':h.lifecycleConfigured?'Ledger unreachable':'Runtime not configured'; - $('#configuration-note').textContent=h.configurationError||(!h.ledgerReachable?'Check that your Canton development ledger is running.':'Connected. No settlement has been claimed until a receipt is returned.'); - } catch(error) {ready=false;$('#canton-badge').textContent='Local app offline';$('#configuration-note').textContent=error.message;} - $('#live-run').disabled=!ready; -} -$('#refresh-health').addEventListener('click',health); const names={BOUNTY_ON_LEDGER:'Bounty created',CLAIM_REQUESTED:'Claim requested',CLAIMED:'Claim accepted',PR_SUBMITTED:'Pull request submitted',VERIFIED:'Merge verified',SETTLED:'Settlement recorded'}; function render(proof) { $('#timeline').innerHTML=proof.steps.map((step,i)=>`
${i+1}. ${escape(names[step.name]||step.name)}Ledger offset ${escape(step.completionOffset)}
Inspect full ledger referencesContract ID${escape(step.contractId)}Update ID${escape(step.updateId)}Template${escape(step.templateId)}
`).join(''); const r=proof.settlementReceipt; $('#receipt-card').classList.remove('hidden'); - $('#receipt-card').innerHTML=`

SETTLEMENT RECEIPT / DEMO VALUE

${escape(r.rewardAmount)} ${escape(r.rewardUnit)}

Repository and issue${escape(r.repository)} #${escape(r.issueNumber)}
Pull request#${escape(r.pullRequest.prNumber)}
Merge commit SHA${escape(r.mergeCommitSha)}
Evidence hash${escape(r.evidenceHash)}
${proof.negativeChecks.map(c=>`
${escape(c.name)}${escape(c.code)}
Inspect rejection response
${escape(JSON.stringify(c.response,null,2))}
`).join('')}

${escape(proof.environment)}. This receipt does not represent a transfer of real money.

`; + $('#receipt-card').innerHTML=`

SETTLEMENT RECEIPT / DEMO VALUE

${escape(r.rewardAmount)} ${escape(r.rewardUnit)}

Repository and issue${escape(r.repository)} #${escape(r.issueNumber)}
Pull request#${escape(r.pullRequest.prNumber)}
Source commit${escape(proof.sourceCommit)}
Merge commit SHA${escape(r.mergeCommitSha)}
Evidence hash${escape(r.evidenceHash)}
Package ID${escape(proof.packageId)}
${proof.negativeChecks.map(c=>`
${escape(c.name)}${escape(c.code)}
Inspect rejection response
${escape(JSON.stringify(c.response,null,2))}
`).join('')}

${escape(proof.environment)}. This receipt does not represent a transfer of real money.

`; $('#download-proof').addEventListener('click',()=>{ const url=URL.createObjectURL(new Blob([JSON.stringify(lastProof,null,2)+'\n'],{type:'application/json'})); const a=document.createElement('a');a.href=url;a.download=`commitledger-proof-${lastProof.runId}.json`;a.click();setTimeout(()=>URL.revokeObjectURL(url),1000); }); } +async function loadProof() { + try { + const {proof,verification}=await request('/api/proof'); + lastProof=proof; + render(proof); + $('#canton-badge').textContent='Verified Canton proof'; + $('#live-status').textContent='Verified deployment proof loaded'; + $('#configuration-note').textContent=`Read-only proof for source ${proof.sourceCommit}. Node/Daml verification commit: ${verification.commit}. The build-time Canton sandbox is intentionally not persistent.`; + } catch(error) { + $('#canton-badge').textContent='Proof unavailable'; + $('#live-status').textContent='Verified proof unavailable'; + $('#configuration-note').textContent=error.message; + } +} +async function health() { + try { + const h=await request('/api/health');ready=h.lifecycleConfigured && h.ledgerReachable; + if(!lastProof) { + $('#canton-badge').textContent=h.verifiedProofAvailable?'Verified proof available':ready?'Canton connected':'Canton runtime offline'; + $('#live-status').textContent=ready?'Ready for another real ledger run':h.verifiedProofAvailable?'Deployment proof available':'Runtime not configured'; + $('#configuration-note').textContent=h.configurationError||(!h.ledgerReachable?'The public service does not keep the build-time sandbox alive.':'Connected. No settlement has been claimed until a receipt is returned.'); + } + } catch(error) { + ready=false; + if(!lastProof) {$('#canton-badge').textContent='App offline';$('#configuration-note').textContent=error.message;} + } + $('#live-run').disabled=!ready; +} +$('#refresh-health').addEventListener('click',async()=>{await health();await loadProof();}); $('#live-form').addEventListener('submit',async event=>{ event.preventDefault();if(!ready)return; lastProof=null;$('#live-run').disabled=true;$('#receipt-card').classList.add('hidden'); @@ -46,4 +65,5 @@ $('#verify-form').addEventListener('submit',async event=>{ try {const {evidence}=await request('/api/github/verify',Object.fromEntries(new FormData(event.target)));$('#github-status').textContent='Merge verified';$('#github-output').textContent=JSON.stringify(evidence,null,2);} catch(error){$('#github-status').textContent='Verification failed';$('#github-output').textContent=error.message;} }); -health(); +await health(); +await loadProof(); diff --git a/web/index.html b/web/index.html index 97db0f5..8cc36ec 100644 --- a/web/index.html +++ b/web/index.html @@ -6,34 +6,34 @@ CommitLedger - Verified work, settled on Canton - +

GITHUB EVIDENCE / DAML AUTHORIZATION / CANTON SETTLEMENT

Work is not settled until the proof is verifiable.

From merged code to inspectable settlement: canonical GitHub evidence, explicit multi-party authorization, and a Canton receipt in one judgeable workflow.

Issue→Bounty→Claim→Pull request→Verify→Settle→Receipt
-
SourceGitHub #5 → PR #6Canonical issue + merged pull request
+
SourceGitHub #69 → PR #73Open issue + canonical merged pull request
Authorization3 explicit rolesMaintainer · Contributor · Verifier
SettlementDEMO_CREDITNon-production test value
-
RuntimeBLOCKEDWaiting for real Canton proof
+
RuntimeVERIFIEDReal Canton proof captured during deploy
-

COMPETITION READINESS

Submission stays fail-closed

BLOCKED · external gates pending
-

Implementation evidence can become verified independently. Repository publication, public judging links, Mana/activity, journal and project-profile requirements remain blocked until they are actually verified.

-
PASSNode verification baseline
BLOCKEDDaml + real Canton proof
BLOCKEDMana / 10-day activity / journal
BLOCKEDPublic judging materials
+

COMPETITION READINESS

Technical proof is green; external gates remain fail-closed

BLOCKED · external gates pending
+

The runtime proof is verified independently. Repository publication, final video, Mana/activity, journal and project-profile requirements remain blocked until they are actually verified.

+
PASSNode verification
PASSDaml + real Canton proof
BLOCKEDMana / activity / journal
BLOCKEDFinal public submission materials
-

LIVE CANTON PROOF

Run the complete six-step lifecycle

Checking runtime
-

The ledger must be configured and reachable. No sample transaction IDs or simulated settlement success are displayed. DEMO_CREDIT is not real money.

-
-

-
No Canton lifecycle has been executed in this session.
+

VERIFIED CANTON PROOF

Inspect the deployed six-step lifecycle

Loading proof artifact
+

A fresh Canton sandbox ran during this deployment. This page exposes the resulting read-only proof bundle. The build-time sandbox is intentionally ephemeral; no sample transaction IDs or simulated settlement success are shown. DEMO_CREDIT is not real money.

+
+

Loading deployment evidence…

+
Loading verified deployment proof…
-

CANONICAL ISSUE

Verify the work source

Waiting
Issue identity and state are fetched server-side from GitHub.
-

CANONICAL MERGE

Verify the merged pull request

Waiting
No merge evidence loaded.
+

CANONICAL ISSUE

Verify the work source

Waiting
Issue identity and state are fetched server-side from GitHub.
+

CANONICAL MERGE

Verify the merged pull request

Waiting
No merge evidence loaded.
-

OFFICIAL JUDGING CRITERIA

Six questions, six visible answers

Value / ProblemGitHub proves code events; Canton enforces who may verify and settle.
ICP / AudienceOpen-source maintainers, foundations and contributor bounty programs.
Metrics / ValidationReal GitHub fixture, deterministic tests and real Canton proof when captured.
GTM MaterialsSingle-repo pilot → foundation/ecosystem bounty program → settlement adapter.
MVP MaterialsGitHub verification + Daml authorization + Canton receipt + attack rejection.
Pitch MaterialsProblem → proof → rejected attacks → receipt → why Canton → pilot path.
+

OFFICIAL JUDGING CRITERIA

Six questions, six visible answers

Value / ProblemGitHub proves code events; Canton enforces who may verify and settle.
ICP / AudienceOpen-source maintainers, foundations and contributor bounty programs.
Metrics / ValidationReal GitHub fixture, deterministic tests and a source-bound Canton proof.
GTM MaterialsSingle-repo pilot → foundation/ecosystem bounty program → settlement adapter.
MVP MaterialsGitHub verification + Daml authorization + Canton receipt + attack rejection.
Pitch MaterialsProblem → proof → rejected attacks → receipt → why Canton → pilot path.
CommitLedger is independent. DEMO_CREDIT is non-production test value. No mainnet, real-money or traction claim is made.
diff --git a/web/styles.css b/web/styles.css index 3a9980a..d14ae22 100644 --- a/web/styles.css +++ b/web/styles.css @@ -1,4 +1,3 @@ :root{font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#eef4f5;background:#061014;line-height:1.5}*{box-sizing:border-box}body{margin:0;background:radial-gradient(circle at 18% -8%,#153b43 0,transparent 32%),linear-gradient(#071216,#061014)}.nav{min-height:76px;display:flex;align-items:center;justify-content:space-between;max-width:1200px;margin:auto;padding:16px 24px;border-bottom:1px solid #1d3339;gap:12px}.brand{font-size:21px;font-weight:800;color:#fff;text-decoration:none;letter-spacing:-.035em}.nav-right{display:flex;gap:8px}.badge,.status{display:inline-flex;align-items:center;font-size:11px;border:1px solid #315058;border-radius:999px;padding:6px 10px;color:#b9ced3;background:#0b181d}main{max-width:1200px;margin:auto;padding:32px 24px 64px}.hero{max-width:1050px;padding:42px 0 48px}.eyebrow,.kicker{font-size:11px;letter-spacing:.15em;color:#91bac3;font-weight:800;margin:0 0 12px}.hero h1{font-size:clamp(40px,6.5vw,78px);line-height:1.03;letter-spacing:-.055em;margin:0}.lead{font-size:20px;color:#a9c0c6;max-width:820px;margin:24px 0}.flow{display:flex;flex-wrap:wrap;gap:9px;align-items:center;margin-top:28px}.flow span{padding:8px 11px;background:#0b171c;border:1px solid #254149;border-radius:9px;color:#a9bec3;font-size:13px}.flow b{color:#55767e}.workspace{display:grid;grid-template-columns:minmax(0,1.35fr) minmax(280px,.65fr);gap:18px;margin-bottom:18px}.panel{border:1px solid #203940;background:rgba(8,20,25,.88);border-radius:18px;padding:24px;min-width:0}.live-panel{margin-bottom:18px;border-color:#365f68;background:linear-gradient(135deg,rgba(11,31,36,.96),rgba(7,20,24,.9))}.panel-head{display:flex;justify-content:space-between;gap:18px;align-items:flex-start}.panel h2{font-size:26px;letter-spacing:-.03em;margin:0 0 18px}.metric{border-top:1px solid #213840;padding:14px 0;display:grid;gap:3px}.metric strong{font-size:14px}.metric span,.muted{color:#a0b6bc;font-size:14px}label{display:grid;gap:7px;font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:#a7bfc5;margin:12px 0;min-width:0}input{width:100%;min-width:0;border:1px solid #29474f;border-radius:10px;background:#061014;color:#eef5f6;padding:12px 13px;font:inherit;text-transform:none;letter-spacing:0}input:focus-visible,button:focus-visible{outline:2px solid #a3d6df;outline-offset:3px}input:disabled{opacity:.75}.row{display:grid;grid-template-columns:1fr 1fr;gap:12px}.row.three{grid-template-columns:.7fr 1.2fr .7fr}button{border:0;border-radius:10px;background:#e6f0f1;color:#061014;padding:12px 16px;font-weight:800;cursor:pointer;margin:6px 6px 0 0}button:disabled{cursor:not-allowed;opacity:.45}.secondary{background:#193740;color:#d8e9ed;border:1px solid #365963}.summary{display:grid;gap:4px;margin-top:18px;padding:15px;border:1px solid #2c4b53;border-radius:12px;background:#08171b}.output{min-height:150px;max-height:360px;overflow:auto;background:#040b0d;border:1px solid #1a3036;border-radius:10px;padding:14px;color:#b2d1d7;font-size:12px;white-space:pre-wrap;overflow-wrap:anywhere}.timeline{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin-top:22px}.timeline-empty,.timeline-error{grid-column:1/-1;padding:18px;border:1px dashed #315059;border-radius:12px;color:#a1b9bf}.timeline-error{border-color:#704047;color:#efbec2}.timeline-step{border:1px solid #29474e;border-radius:12px;padding:14px;background:#071519;min-width:0}.timeline-step strong,.timeline-step span{display:block}.timeline-step strong{font-size:14px;margin-bottom:5px}.timeline-step span{font-size:12px;color:#a1bac1}.timeline-step code,.receipt code{display:block;overflow-wrap:anywhere;font-size:11px;margin:5px 0;color:#c1dce2}details{margin-top:8px}summary{cursor:pointer;font-size:12px}.receipt{margin-top:16px;padding:20px;border:1px solid #47717a;border-radius:14px;background:rgba(15,38,44,.72);overflow-wrap:anywhere}.hidden{display:none}.receipt h3{margin:0 0 15px;font-size:22px}.receipt-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}.receipt-grid span,.receipt-grid strong{display:block}.receipt-grid span{font-size:11px;color:#a2bdc5}.receipt-grid strong{font-size:13px;margin-top:4px}.negative{border-top:1px solid #35565e;margin-top:12px;padding-top:12px}footer{max-width:1200px;margin:auto;padding:24px;border-top:1px solid #1d3339;color:#98b0b7;font-size:12px}@media(max-width:900px){.workspace,.timeline,.receipt-grid,.row,.row.three{grid-template-columns:1fr}.hero{padding-top:18px}.panel-head{display:block}.status{margin-bottom:10px}.panel{padding:20px}main{padding:20px 16px 40px}.nav{padding:14px 16px}.nav-right .badge:last-child{display:none}.eyebrow{line-height:1.8}.lead{font-size:17px}} .readiness-panel,.criteria-panel{margin-bottom:18px}.status.blocked{border-color:#7a6540;color:#e4bc68}.readiness-grid,.criteria-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin-top:18px}.readiness-grid>div,.criteria-grid article{border:1px solid #29474f;border-radius:12px;padding:14px;background:#071519;min-width:0}.readiness-grid strong,.readiness-grid span,.criteria-grid strong,.criteria-grid span{display:block}.readiness-grid strong,.criteria-grid strong{font-size:13px;margin-bottom:5px}.readiness-grid span,.criteria-grid span{font-size:12px;color:#a1bac1}.criteria-grid{grid-template-columns:repeat(3,minmax(0,1fr))}@media(max-width:900px){.readiness-grid,.criteria-grid{grid-template-columns:1fr}} - -.evidence-rail{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin:0 0 18px}.evidence-rail article{border:1px solid #29474f;border-radius:14px;padding:16px;background:linear-gradient(180deg,rgba(11,24,29,.94),rgba(7,21,25,.9));min-width:0}.evidence-rail span,.evidence-rail strong,.evidence-rail small{display:block}.evidence-rail span{font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:#91bac3;font-weight:800}.evidence-rail strong{font-size:18px;letter-spacing:-.02em;margin:6px 0 3px;color:#eef4f5}.evidence-rail small{font-size:12px;color:#a0b6bc}.evidence-rail .runtime-blocked{border-color:#7a6540}.evidence-rail .runtime-blocked strong{color:#e4bc68}@media(max-width:900px){.evidence-rail{grid-template-columns:1fr 1fr}}@media(max-width:560px){.evidence-rail{grid-template-columns:1fr}} +.evidence-rail{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin:0 0 18px}.evidence-rail article{border:1px solid #29474f;border-radius:14px;padding:16px;background:linear-gradient(180deg,rgba(11,24,29,.94),rgba(7,21,25,.9));min-width:0}.evidence-rail span,.evidence-rail strong,.evidence-rail small{display:block}.evidence-rail span{font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:#91bac3;font-weight:800}.evidence-rail strong{font-size:18px;letter-spacing:-.02em;margin:6px 0 3px;color:#eef4f5}.evidence-rail small{font-size:12px;color:#a0b6bc}.evidence-rail .runtime-verified{border-color:#3f7161}.evidence-rail .runtime-verified strong{color:#9fe0c4}@media(max-width:900px){.evidence-rail{grid-template-columns:1fr 1fr}}@media(max-width:560px){.evidence-rail{grid-template-columns:1fr}} From f83aa82e5661fb38be2d35c090bee340e0bdd8bb Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Thu, 1 Oct 2026 14:14:03 +0600 Subject: [PATCH 134/144] docs: retarget CommitLedger to Colosseum and Ideathon --- README.md | 93 +++++++++-------- docs/COMPETITION_TARGETS.md | 167 ++++++++++++++++++++++++++++++ docs/DELIVERY_STATUS.md | 194 +++++++++++++++-------------------- docs/FINAL_CHECKLIST.md | 149 ++++++++++++++++----------- docs/NEXT_SESSION_HANDOFF.md | 136 ++++++++++++------------ docs/SUBMISSION_DRAFT.md | 182 +++++++++++++++++++++++--------- 6 files changed, 590 insertions(+), 331 deletions(-) create mode 100644 docs/COMPETITION_TARGETS.md diff --git a/README.md b/README.md index 4d25a02..13c960f 100644 --- a/README.md +++ b/README.md @@ -2,33 +2,58 @@ **Canton-native settlement infrastructure for verified open-source work.** -CommitLedger binds a real GitHub bounty issue and merged pull request to role-separated Daml authorization and an auditable Canton settlement receipt. +CommitLedger binds real contribution evidence to role-separated authorization and an auditable settlement receipt. -## Competition thesis +## Current competition targets + +CommitLedger is currently being prepared for exactly two programs: + +1. **Crypto World's Fair — Colosseum** +2. **Ideathon Bangladesh 2026** + +See [Competition Targets](docs/COMPETITION_TARGETS.md) for the canonical plan, eligibility-first rules, and next-session order. + +HackCanton Season 3 is no longer an active submission target. Its technical work is preserved as product proof/history. + +## Product thesis `GitHub Issue -> Bounty -> Claim -> Pull Request -> Merge Evidence -> Canton Settlement -> SettlementReceipt` -Canton is the trust and settlement layer, not a cosmetic integration. Removing Canton removes the product's authorization, provenance, replay protection and final settlement record. +Canton is the current trust and settlement layer, not a cosmetic integration. Removing the ledger authorization layer removes the product's role controls, provenance, replay protection and final settlement record. ## Core guarantees - **Maintainer** creates the bounty, accepts the claim and settles verified work. - **Contributor** creates the claim and submits the exact PR. -- **Verifier** can attest only canonical GitHub merge evidence. -- Merge evidence includes the **exact bounty issue number**. +- **Verifier** can attest only canonical merge evidence. +- Merge evidence includes the exact bounty issue number. - Wrong issue, wrong SHA, unmerged work and unauthorized actions are rejected. - Settlement consumes the verified contract, blocking duplicate settlement. - `DEMO_CREDIT` is explicitly non-production test value. -## Real GitHub fixture +## Verified proof fixture — not the product repository + +The current reproducible external evidence fixture is: + +- Repository: `Saidur-droid/MergeEarn` +- Open issue: `#69` +- Merged PR: `#73` +- Contributor: `Saidur-droid` +- Base branch: `main` + +**CommitLedger is the product. MergeEarn is only the external GitHub issue/PR evidence source used to prove that CommitLedger can verify and settle work from another repository.** + +Frozen fully executed technical proof commit: -The reproducible competition fixture is: +`26bd992787401f6458f6685d2ab76aacd05eab4e` -- Open issue: `#5` -- Merged PR: `#6` -- PR #6 references issue #5 without closing it. +Live proof: -This lets a judge repeatedly prove the issue -> merged PR trust boundary without fake users or fake traction. +https://commitledger-proof-final.onrender.com + +Proof JSON: + +https://commitledger-proof-final.onrender.com/api/proof ## Run locally @@ -41,51 +66,37 @@ npm start Open `http://127.0.0.1:4173`. -Because this repository is currently private, set `GITHUB_TOKEN` with read access before using the canonical GitHub verifier. +Because this repository may be private during preparation, set `GITHUB_TOKEN` with the required read access before using the canonical GitHub verifier locally. ## Run the full Canton proof -Configure `.env.example` values for: -- Canton JSON Ledger API URL; -- package ID; -- three distinct parties; -- one LocalNet token with rights to all demo parties, or role-specific tokens. - -Then: +Configure the documented Canton environment values, then run: ```bash -npm run demo:full +bash scripts/run-local-proof.sh ``` -The runner automatically captures each Canton `updateId`, `completionOffset` and active `contractId`, executes negative authorization/replay checks, and finishes at the active `SettlementReceipt`. - -See [Judge Runbook](docs/JUDGE_RUNBOOK.md) for the exact operator path. +The proof path reruns tests, builds/tests Daml, starts a fresh Canton sandbox, captures each ledger transition, executes negative authorization/replay checks, validates one coherent evidence bundle, and finishes at SettlementReceipt. ## Canton / Daml -The Daml project is under `daml/` and is pinned to the stable open-source DPM SDK bundle **3.5.12**. +The Daml project is under `daml/` and uses the pinned open-source DPM SDK bundle **3.5.12**. -Verification commands: +## Planning and governance -```bash -bash scripts/bootstrap-dpm.sh -bash scripts/verify-all.sh -``` - -## Competition governance - -- [Locked decisions](LOCKED_DECISIONS.md) -- [Competition compliance](docs/COMPETITION_COMPLIANCE.md) -- [Winning standard](docs/WINNING_STANDARD.md) -- [Rules snapshot](docs/RULES_SNAPSHOT.md) +- [Competition Targets](docs/COMPETITION_TARGETS.md) +- [Next Session Handoff](docs/NEXT_SESSION_HANDOFF.md) +- [Dual-Program Final Checklist](docs/FINAL_CHECKLIST.md) +- [Dual-Program Submission Master Draft](docs/SUBMISSION_DRAFT.md) +- [Delivery Status](docs/DELIVERY_STATUS.md) - [Architecture](docs/ARCHITECTURE.md) -- [Threat model](docs/THREAT_MODEL.md) -- [Judge runbook](docs/JUDGE_RUNBOOK.md) -- [Evidence manifest](docs/EVIDENCE_MANIFEST.md) -- [Build status](docs/BUILD_STATUS.md) +- [Threat Model](docs/THREAT_MODEL.md) +- [Evidence Manifest](docs/EVIDENCE_MANIFEST.md) ## Integrity -CommitLedger does not claim Canton Coin transfer, fiat settlement, external adoption, mainnet usage, green CI, or runtime Canton success unless that evidence has actually been produced. +CommitLedger does not claim Canton Coin transfer, fiat settlement, external adoption, mainnet usage, revenue, paying customers, or production custody without evidence. + +`DEMO_CREDIT` is test value only. Canton is a registered trademark of Digital Asset (Switzerland) GmbH. CommitLedger is an independent project and is not sponsored or endorsed by Digital Asset. diff --git a/docs/COMPETITION_TARGETS.md b/docs/COMPETITION_TARGETS.md new file mode 100644 index 0000000..a8856d8 --- /dev/null +++ b/docs/COMPETITION_TARGETS.md @@ -0,0 +1,167 @@ +# Competition Targets — 1 October 2026 + +**This is the canonical competition plan for CommitLedger.** + +## Active submission targets + +CommitLedger will now be prepared for exactly these two programs: + +1. **Crypto World's Fair — Colosseum** +2. **Ideathon Bangladesh 2026** + +These are the active targets. If a future session asks, “Which programs are we participating in?”, answer with these two unless this file is deliberately updated after a fresh rules/eligibility review. + +## Retired target + +HackCanton Season 3 is **not an active submission target anymore** because the required AppsFactory account-registration/activity gates were not completed in time. + +Do not spend more time on HackCanton submission work. + +The Canton/Daml implementation and verified proof created during that effort remain valuable technical evidence and are preserved. Do not delete or rewrite that history. + +## Frozen technical proof + +The last fully executed, source-bound technical proof is frozen at: + +`26bd992787401f6458f6685d2ab76aacd05eab4e` + +Canonical proof service: + +https://commitledger-proof-final.onrender.com + +Proof endpoint: + +https://commitledger-proof-final.onrender.com/api/proof + +That proof includes: +- Node 86/86 PASS; +- DPM 3.5.12; +- Daml build and Daml Script PASS; +- a fresh Canton sandbox; +- six real ledger transitions; +- SettlementReceipt; +- wrong-issue rejection; +- unauthorized-settlement rejection; +- duplicate/replay rejection; +- exact source-commit binding. + +Documentation-only commits made after the frozen proof do **not** become runtime-proven commits automatically. If executable code changes later, rerun the full proof on the new exact code commit. + +## Global rule before any new competition work + +**Eligibility first. Build second.** + +Before changing code for any event, verify and record: +1. registration is still open; +2. the owner is eligible by age/country/team rules; +3. participation and submission do not require a paid product/service; +4. pre-existing projects/code are allowed or can be disclosed; +5. required build window and judging scope; +6. required chain/stack/track; +7. submission deadline and mandatory deliverables. + +If any mandatory gate is unknown, do not start engineering until it is resolved. + +**Owner constraint:** use free options only. Do not introduce paid tools/services unless the owner explicitly changes this rule. + +--- + +# Target 1 — Crypto World's Fair — Colosseum + +## Current plan + +Treat CommitLedger as a startup/product submission, not as a HackCanton project. + +Core product thesis: + +> CommitLedger is verifiable work-to-settlement infrastructure for open-source ecosystems and contributor programs. It turns canonical contribution evidence into an authorization-controlled settlement workflow with replay protection and an auditable receipt. + +## What should stay + +Keep the proven product core: +- GitHub evidence verification; +- role-separated authorization; +- Canton/Daml state machine; +- replay protection; +- SettlementReceipt; +- structured negative/security proof; +- public read-only proof page. + +Do **not** rewrite the core merely to look different for Colosseum. + +## What should improve for Colosseum + +Primary work should be submission/business quality: +- founder/market framing; +- target-user clarity; +- credible market size; +- demand/user validation; +- go-to-market plan; +- concise presentation video; +- concise product demo; +- transparent pre-existing-work disclosure; +- clear explanation of what was built during the competition window. + +Any competition-specific code change must be made on a separate branch and only after official rules are re-verified. + +## Current deadline note + +Current research recorded a submission deadline of **12 October 2026**. + +Before registration or final submission, re-open the official Colosseum rules and verify the live deadline, eligibility, deliverables, and pre-existing-code rules again. Do not rely on this repository note as a substitute for the live rules. + +--- + +# Target 2 — Ideathon Bangladesh 2026 + +## Current plan + +Use the same CommitLedger core, but position it around business value and a credible adoption path. + +Suggested business framing: +- auditable contributor/work settlement; +- open-source and ecosystem bounty operations; +- remote software-work verification; +- organizations that need evidence-linked authorization before settlement. + +Do not fabricate Bangladeshi customers, revenue, pilots, or adoption. + +## What should stay + +No core-code rewrite is currently planned. + +Use the existing technical proof as product credibility, then strengthen: +- problem statement; +- customer segment; +- Bangladesh-relevant business use cases where justified; +- go-to-market; +- pricing/business model hypothesis; +- validation interviews/feedback; +- pitch clarity. + +## Current deadline note + +Current research recorded a Round 1 deadline of **20 October 2026 at 11:59 PM Bangladesh time**. + +Before applying or submitting, re-verify the official live rules, deadline, team/eligibility requirements, and application fields. + +--- + +# Next-session order + +When work resumes: + +1. Read this file first. +2. Re-verify **Colosseum** live rules and registration status. +3. Register/apply first and retain confirmation evidence. +4. Re-verify **Ideathon Bangladesh 2026** live rules and registration status. +5. Register/apply first and retain confirmation evidence. +6. Freeze the competition-specific requirements in repo docs. +7. Build the Colosseum submission package. +8. Build the Ideathon submission package. +9. Only make product/code changes if a verified competition requirement or concrete product defect requires them. +10. Never claim a submission is complete until the relevant portal provides confirmation/receipt. + +## Not doing today + +No product engineering, video production, portal submission, or competition-specific feature work is required on 1 October 2026 after this planning update. diff --git a/docs/DELIVERY_STATUS.md b/docs/DELIVERY_STATUS.md index c89405c..e96bc9f 100644 --- a/docs/DELIVERY_STATUS.md +++ b/docs/DELIVERY_STATUS.md @@ -1,116 +1,84 @@ -# Delivery status — 30 September 2026 +# Delivery status — 1 October 2026 Delivery branch: `fix/verified-delivery-20260929`. -## Current candidate - -- Exact current head: `722df9f69458cba0080d687105b4a0fb00f5c4d6`. -- PR #8 is still open and draft. Keep it draft until submission-side gates below are completed. -- Canonical Render service: `commitledger-proof-final`. -- Canonical public service URL: https://commitledger-proof-final.onrender.com -- Render deploy `dep-daucnqff3r2c73ep69u0` for the exact head above is **live**. -- The older Render service `commitledger-proof` is a discarded/dummy duplicate and is not evidence of final delivery status. - -## Verified technical proof - -The exact current candidate has completed the provider-side proof path on Render. - -Verified from the successful Render build/deploy: - -- Node suite passes: **86/86**. -- DPM **3.5.12** installs/runs. -- Daml build passes and a fresh DAR is created. -- Daml Script tests pass, including happy path and rejection/replay cases. -- Java 21 runtime is bootstrapped for Daml Script execution. -- A fresh local Canton sandbox starts and reports ready. -- The DAR package ID is derived canonically from the built DAR using `dpm damlc inspect-dar --json`. -- Three distinct local demo parties are allocated. -- Real GitHub evidence fixture used by the proof is: - - open issue: `Saidur-droid/MergeEarn#69` - - merged PR: `Saidur-droid/MergeEarn#73` - - contributor: `Saidur-droid` - - target branch: `main` -- The full Issue -> Bounty -> Claim -> PR -> Verify -> Settle -> SettlementReceipt lifecycle completes on Canton. -- Wrong-evidence rejection is captured. -- Unauthorized settlement rejection is captured. -- Duplicate/replay settlement rejection is captured as a structured Canton `CONTRACT_NOT_FOUND` rejection. -- The proof contains a real settlement receipt using clearly labelled non-production `DEMO_CREDIT`. -- Verification and Canton proof are bound to one exact source commit. -- Render build completes successfully. -- The web service binds to Render correctly and the final deploy is live. - -This closes the technical runtime blocker that previously depended on a desktop/Codespace/GitHub Actions runner. - -## Important architecture notes - -- The proof backend is Render provider-side execution, not GitHub Actions and not the user's desktop. -- Render is authorized directly against the private CommitLedger repository; no other project repository is used as a runner or mirror. -- The proof uses a local ephemeral Canton sandbox during the Render build. The public web service is the judge-facing web service; it does not claim a persistent production Canton deployment. -- `DEMO_CREDIT` is test value only. No fiat, Canton Coin, MainNet, custody, revenue, customer traction, or organizer endorsement is claimed. -- The old duplicate Render service `commitledger-proof` should be ignored and manually removed later if desired. The available Render connector cannot delete/suspend it. - -## Remaining work — do this next - -The coding/runtime-debugging phase is effectively complete. Do not reopen architecture work unless a new concrete regression appears. - -### 1. Evidence/status cleanup -- Update PR #8 body so it no longer says the real Canton proof is missing. -- Keep the exact successful commit/deploy IDs in the PR and submission notes. -- Confirm no new code changes have invalidated the proof commit before recording the final video. If code changes after `722df9f...`, rerun the full proof on the new exact head. - -### 2. Judge browser QA -- Test the canonical Render URL on desktop. -- Test the same judge flow on a narrow/mobile viewport. -- Confirm the page loads without credentials. -- Confirm IDs/evidence are legible and no secret/private data is exposed. -- Test all final links in a logged-out/private browser window. - -### 3. Final <=5-minute demo video -Record only after browser QA passes. The video should show: -1. problem + three roles; -2. real GitHub issue/PR evidence; -3. verified Canton six-step lifecycle; -4. wrong-evidence / unauthorized / replay rejection; -5. SettlementReceipt and `DEMO_CREDIT` disclaimer; -6. why Canton is necessary; -7. Track 1 business/pilot angle. - -Review the full exported video before publishing. - -### 4. Public/submission materials -Before submission: -- make the repository accessible to a logged-out judge when ready; -- publish/verify the demo video; -- verify pitch/project page; -- populate final repository/demo/video/pitch/evidence links in `docs/SUBMISSION_DRAFT.md`; -- verify every URL from a private/logged-out browser. - -### 5. AppsFactory / competition account gates -Still require human/account verification: -- required 1,000 Mana; -- minimum required platform activity; -- project profile complete; -- journal non-empty; -- team/eligibility valid; -- pre-existing code disclosure accurate; -- hackathon-period work identifiable. - -### 6. Final submission -- Review the final submission wording against actual evidence only. -- Submit through the competition portal. -- Retain the submission/portal receipt. -- Only after all mandatory gates pass should PR #8 be marked ready/merged and the project be called submission-ready. - -## Resume instruction for the next session - -Start here, in this order: - -1. Read this file and `docs/FINAL_CHECKLIST.md`. -2. Confirm Render service `commitledger-proof-final` is still live on the current exact Git head. -3. Do **not** repeat the old desktop/GitHub Actions debugging unless Render is unusable. -4. Complete browser QA. -5. Record/review the <=5-minute video. -6. Complete public links and AppsFactory gates. -7. Finish portal submission and retain receipt. - -Do not ask the owner to restate the technical history; this document is the handoff. +## Product/technical status + +The CommitLedger product core is complete. + +The last fully executed, source-bound technical proof is: + +`26bd992787401f6458f6685d2ab76aacd05eab4e` + +Canonical Render service: + +`commitledger-proof-final` + +Live page: + +https://commitledger-proof-final.onrender.com + +Proof endpoint: + +https://commitledger-proof-final.onrender.com/api/proof + +Successful proven deploy: + +`dep-daujs4m0tbcc73dn0c80` + +PR #8 was confirmed open, non-draft, ready for review and mergeable at the proven technical state. + +## Verified proof + +- Node 86/86 PASS. +- DPM 3.5.12 PASS. +- Daml build PASS. +- Daml Script tests PASS. +- Java 21 runtime works. +- Fresh Canton sandbox works. +- DAR package identity extracted canonically. +- Three distinct demo parties allocated. +- External GitHub proof fixture: `Saidur-droid/MergeEarn#69` -> merged PR `#73`. +- Six real Canton transitions completed. +- SettlementReceipt generated. +- Wrong-issue evidence rejected. +- Unauthorized contributor settlement rejected. +- Duplicate/replay settlement rejected. +- Evidence source-commit bound. +- Public proof bundle available. + +**MergeEarn is only the external evidence fixture; CommitLedger is the product.** + +## Competition direction changed + +HackCanton Season 3 is no longer an active submission target because the required account-registration/activity gates were not completed in time. + +The active targets are now: + +1. **Crypto World's Fair — Colosseum** +2. **Ideathon Bangladesh 2026** + +Canonical competition plan: + +[COMPETITION_TARGETS.md](COMPETITION_TARGETS.md) + +## Engineering state + +Do not reopen product engineering unless: +- a real QA defect appears; or +- a verified requirement from one of the two active competitions requires a change. + +Documentation-only planning commits may advance the branch without invalidating the statement that `26bd992...` is the frozen proven **code** state. Do not describe a later docs-only branch head as runtime-proven. + +If executable code changes, rerun the entire Render proof on the exact new code commit. + +## Next work + +Next session: +1. verify Colosseum live rules and registration; +2. register/apply and save confirmation; +3. verify Ideathon Bangladesh live rules and registration; +4. register/apply and save confirmation; +5. then prepare competition-specific business/pitch/demo materials. + +Owner constraint: free tools/services only. diff --git a/docs/FINAL_CHECKLIST.md b/docs/FINAL_CHECKLIST.md index 1f78e07..3a5a4b4 100644 --- a/docs/FINAL_CHECKLIST.md +++ b/docs/FINAL_CHECKLIST.md @@ -1,60 +1,89 @@ -# HackCanton Season 3 Final Checklist - -A source-code check is not runtime proof. A runtime proof is not an account/portal proof. - -## Implementation gates -- [x] Repository-qualified issue matching implemented and Node-tested. -- [x] Merge commit SHA / branch reachability checks implemented and Node-tested. -- [x] Infrastructure errors excluded from security-rejection proof. -- [x] Competition-readiness PASS/BLOCKED/FAIL model implemented. -- [x] Full current Node suite passes on the proven candidate commit `722df9f69458cba0080d687105b4a0fb00f5c4d6` (**86/86**). -- [x] DPM 3.5.12 installs in the Render proof environment. -- [x] Daml build passes in the Render proof run. -- [x] Daml Script tests pass in the Render proof run. -- [x] Current DAR is deployed to a fresh real local Canton sandbox during the proof run. -- [x] Real Issue → Bounty → Claim → PR → Verify → Settle → Receipt flow succeeds. -- [x] Wrong-evidence rejection is captured from Canton/Daml. -- [x] Unauthorized settlement rejection is captured from Canton/Daml. -- [x] Duplicate/replay settlement rejection is captured from Canton/Daml. -- [x] Evidence bundle is coherent: one source commit and one proof run. -- [ ] Desktop/mobile judge flow passes browser QA. -- [ ] No secrets appear in final tracked/public files, screenshots, published evidence, or video. **Final publication review still required.** -- [x] Track 1 business brief exists. -- [x] Pilot plan exists. -- [x] AI disclosure exists. -- [x] Hackathon work disclosure exists. -- [ ] Final ≤5-minute demo video is recorded and reviewed. - -### Proven deployment -- Canonical service: `commitledger-proof-final` -- URL: https://commitledger-proof-final.onrender.com -- Proven source commit: `722df9f69458cba0080d687105b4a0fb00f5c4d6` -- Successful Render deploy: `dep-daucnqff3r2c73ep69u0` -- Status verified: **live** -- Ignore the older duplicate service `commitledger-proof`; it is not the canonical delivery. - -## Official competition gates -- [x] Target track is Track 1 — RWA & Business Workflows. -- [x] Meaningful Canton integration is an explicit hard requirement in the design and is now runtime-proven on the candidate above. -- [x] Submission deadline recorded as 2026-10-09 23:59 UTC. -- [ ] Public repository available to a logged-out judge. **Deferred by owner until final phase.** -- [ ] Public demo/video/pitch/project page accessible without requesting access. **Final logged-out verification pending.** -- [ ] Submitting account has burned/accrued the required 1,000 Mana. -- [ ] At least 10 days of required platform activity is satisfied. -- [ ] Project profile is complete. -- [ ] Journal is non-empty. -- [ ] Team/eligibility information is valid. -- [ ] Pre-existing code is disclosed and qualifying delivery-period work is identifiable. -- [ ] All final links tested in a private/logged-out browser window. -- [ ] Participant reviews final submission and retains portal receipt. - -## Next session order -1. Browser QA (desktop + mobile). -2. Final publication/secrets review. -3. Record and review the ≤5-minute video. -4. Make repository/materials judge-accessible at the chosen final moment. -5. Complete AppsFactory account/profile/journal/Mana/activity/eligibility gates. -6. Populate and test every final submission link. -7. Submit and retain the receipt. - -Do not call CommitLedger fully submission-ready while any mandatory official gate above is unchecked. +# CommitLedger — Dual-Program Final Checklist + +Canonical active targets: +1. **Crypto World's Fair — Colosseum** +2. **Ideathon Bangladesh 2026** + +Do not use the old HackCanton/AppsFactory checklist as the active plan. + +## Frozen technical baseline + +- [x] Product core implemented. +- [x] Node tests: 86/86 PASS on proven build. +- [x] DPM 3.5.12 PASS. +- [x] Daml build PASS. +- [x] Daml Script tests PASS. +- [x] Fresh Canton sandbox proof completed. +- [x] Six-step ledger lifecycle completed. +- [x] SettlementReceipt generated. +- [x] Wrong-issue rejection captured. +- [x] Unauthorized-settlement rejection captured. +- [x] Duplicate/replay rejection captured. +- [x] Public read-only proof endpoint exists. +- [x] Frozen technical proof commit: `26bd992787401f6458f6685d2ab76aacd05eab4e`. +- [x] Canonical Render service: `commitledger-proof-final`. + +Documentation/planning commits after the proven commit are not automatically runtime-proven. + +## Universal eligibility gate — must pass before coding + +For each target: +- [ ] Registration is still open. +- [ ] Participant/team eligibility confirmed. +- [ ] Free participation path confirmed. +- [ ] Pre-existing-code/project rule confirmed. +- [ ] Competition build/judging window confirmed. +- [ ] Required chain/stack/track confirmed. +- [ ] Required deliverables confirmed. +- [ ] Deadline confirmed from live official source. +- [ ] Registration/application confirmation saved. + +If any item above is unknown, do not start competition-specific engineering. + +## Colosseum checklist + +- [ ] Live rules re-verified. +- [ ] Registration completed. +- [ ] Confirmation/receipt retained. +- [ ] Pre-existing-work disclosure prepared. +- [ ] Competition-window work identified from Git history. +- [ ] Founder/market positioning finalized. +- [ ] Target-user validation collected. +- [ ] Market/GTM story finalized. +- [ ] Presentation video recorded and reviewed. +- [ ] Product demo recorded and reviewed. +- [ ] Repository/access requirement satisfied. +- [ ] Every submission link tested logged-out where applicable. +- [ ] Final portal review completed. +- [ ] Submission completed. +- [ ] Submission receipt retained. + +## Ideathon Bangladesh 2026 checklist + +- [ ] Live rules re-verified. +- [ ] Registration/application completed. +- [ ] Confirmation retained. +- [ ] Problem and customer segment finalized. +- [ ] Bangladesh relevance stated only with defensible evidence. +- [ ] Business model hypothesis finalized. +- [ ] Market sizing prepared. +- [ ] Validation evidence collected. +- [ ] Pitch/application materials completed. +- [ ] Required links/files tested. +- [ ] Final application review completed. +- [ ] Submission completed. +- [ ] Submission receipt retained. + +## Code-change rule + +Do not change product code merely to “refresh” the project for a new competition. + +Only change executable code when: +- a verified competition requirement requires it; or +- a real product/QA defect is found. + +If executable code changes, rerun the complete Render proof and bind evidence to the exact new code commit. + +## Owner constraint + +Use free tools/services only unless the owner explicitly approves otherwise. diff --git a/docs/NEXT_SESSION_HANDOFF.md b/docs/NEXT_SESSION_HANDOFF.md index a4cce2c..71ca60d 100644 --- a/docs/NEXT_SESSION_HANDOFF.md +++ b/docs/NEXT_SESSION_HANDOFF.md @@ -2,87 +2,85 @@ **Read this first. Do not ask the owner to repeat the project history.** -## Where we stopped +## Active competition plan -Technical runtime work is green on the exact commit: +CommitLedger now has exactly two active submission targets: -`722df9f69458cba0080d687105b4a0fb00f5c4d6` +1. **Crypto World's Fair — Colosseum** +2. **Ideathon Bangladesh 2026** + +Canonical plan: [COMPETITION_TARGETS.md](COMPETITION_TARGETS.md) + +HackCanton Season 3 is no longer an active submission target. Do not resume AppsFactory/Mana/journal work. + +## Technical product status + +Product engineering is complete unless a new concrete defect or a verified new-competition requirement appears. + +Frozen fully proven technical commit: + +`26bd992787401f6458f6685d2ab76aacd05eab4e` Canonical Render service: `commitledger-proof-final` -Public URL: +Live proof page: https://commitledger-proof-final.onrender.com -Successful Render deploy: - -`dep-daucnqff3r2c73ep69u0` - -Deploy status: **live**. - -The similarly named `commitledger-proof` service is an older dummy/duplicate service. Ignore its failed status. It is not the canonical proof service. - -## What was actually proven - -- Node tests: 86/86 pass. -- DPM 3.5.12 works. -- Daml build passes. -- Daml Script tests pass. -- Temurin Java 21 is available in the proof environment. -- Fresh Canton sandbox starts and becomes ready. -- Built DAR package identity is derived from the DAR itself. -- Three distinct demo parties are allocated. -- Public real GitHub fixture passes preflight: MergeEarn issue #69 (open) + PR #73 (merged, references #69). -- Six-step Canton lifecycle completes through SettlementReceipt. -- Wrong-evidence rejection is captured. -- Unauthorized settlement rejection is captured. -- Duplicate/replay rejection is captured. -- Evidence is source-commit bound. -- Render build succeeds. -- Judge-facing web service deploys live on Render. - -## Root causes already solved — do not re-debug these without new evidence - -- GitHub hosted Actions jobs failing before steps started. -- Offline desktop execution dependency. -- missing Java in Render build image. -- brittle DAR package-ID extraction. -- duplicate package-ID env/file propagation. -- invalid closed GitHub issue fixture. -- Canton 3.5 error-envelope normalization. -- server binding to 127.0.0.1 instead of 0.0.0.0 on Render. -- forwarded HTTPS/public-host handling. -- brittle public-host regression test. - -## What remains - -This is now a **submission preparation** project, not a runtime debugging project. - -Remaining mandatory work: -- desktop browser QA; -- mobile/narrow viewport QA; -- final secrets/publication review; -- final <=5-minute video; -- public repo access for judges at the final chosen time; -- public video/pitch/project/evidence links; -- logged-out/private-window verification of all links; -- AppsFactory Mana/activity/profile/journal/team/eligibility gates; -- final pre-existing-work/hackathon-period disclosure review; -- portal submission; -- retain final submission receipt. +Proof endpoint: -## Exact next action +https://commitledger-proof-final.onrender.com/api/proof + +Successful proven deploy: + +`dep-daujs4m0tbcc73dn0c80` + +Verified proof: +- Node 86/86 PASS; +- DPM 3.5.12 PASS; +- Daml build PASS; +- Daml Script tests PASS; +- fresh Canton sandbox; +- MergeEarn #69 -> PR #73 as the external GitHub evidence fixture; +- six real Canton transitions; +- SettlementReceipt; +- three structured negative/rejection proofs; +- exact source-commit binding. -Next time, begin with **browser QA of https://commitledger-proof-final.onrender.com**. +**Important:** MergeEarn is only the external GitHub evidence fixture. CommitLedger is the product. + +## Branch/proof rule + +Planning/documentation commits may be newer than the frozen technical proof. + +Do not call a newer documentation commit “runtime proven.” + +If executable code changes, rerun the full Render proof on the exact new code commit before using it as technical evidence. + +## Global competition rule + +Before engineering for either target, verify: +- registration open; +- participant/team eligibility; +- no paid-service requirement; +- pre-existing-code policy; +- judging/build window; +- required stack/track; +- submission deadline and deliverables. + +Registration/eligibility comes before feature work. + +## Exact next action -If browser QA passes, move directly to the final video and submission package. +Next session begins with **Colosseum registration/rules verification**, not coding. -Do not change production/proof code unless QA finds a concrete defect. Any code change after the proven commit invalidates the exact-commit proof and requires a fresh proof run. +Then: +1. register/apply to Colosseum and retain confirmation; +2. verify/register for Ideathon Bangladesh 2026 and retain confirmation; +3. write the final requirements matrix; +4. prepare competition-specific pitch/demo/submission materials; +5. only then consider any necessary changes. -See: -- `docs/DELIVERY_STATUS.md` -- `docs/FINAL_CHECKLIST.md` -- `docs/SUBMISSION_DRAFT.md` -- `docs/DEMO_SCRIPT.md` +Owner constraint: **free tools/services only** unless explicitly changed. diff --git a/docs/SUBMISSION_DRAFT.md b/docs/SUBMISSION_DRAFT.md index 13f5c4f..9224a37 100644 --- a/docs/SUBMISSION_DRAFT.md +++ b/docs/SUBMISSION_DRAFT.md @@ -1,69 +1,155 @@ -# CommitLedger — HackCanton Season 3 Submission Draft +# CommitLedger — Dual-Program Submission Master Draft -**Status: draft. Do not submit until `docs/FINAL_CHECKLIST.md` has no critical implementation failures and all external account/publication gates are verified.** +**Active targets** +1. Crypto World's Fair — Colosseum +2. Ideathon Bangladesh 2026 -## Track -**Track 1 — Real-World Assets (RWA) & Business Workflows** +See [COMPETITION_TARGETS.md](COMPETITION_TARGETS.md) for the canonical plan and eligibility-first rule. -## One-line description -CommitLedger turns verified GitHub contribution work into an authorization-controlled Canton settlement workflow with an auditable receipt. +## Shared one-line description -## Problem -GitHub can prove that an issue exists and a pull request merged, but it does not by itself enforce which party may verify a bounty claim or which party may settle it. +CommitLedger turns verified contribution work into an authorization-controlled settlement workflow with an auditable receipt. -## Solution -CommitLedger binds a real GitHub issue and merged pull request to a Daml state machine: +## Shared problem -Issue → Bounty → Claim → Pull Request → Merge Verification → Settlement → SettlementReceipt. +A code host can prove that an issue exists and a pull request merged, but that evidence alone does not define who is authorized to verify a claim, who may settle it, or how duplicate settlement is prevented. -The backend independently validates repository identity, issue binding, contributor policy, base branch, merged state, head SHA, merge commit SHA, and merge evidence before the ledger workflow advances. +## Shared solution -## Why Canton -Canton/Daml is used for meaningful workflow state and authorization: distinct maintainer, contributor, and verifier roles; authorized contract choices; inspectable update/contract identifiers; and consumed-contract replay protection. GitHub remains the external evidence source. +CommitLedger binds canonical contribution evidence to an authorization-controlled workflow: -## Target users / ICP -Open-source maintainers, foundations, ecosystem grant programs, and organizations operating contributor bounty workflows. +Issue -> Bounty -> Claim -> Pull Request -> Merge Verification -> Settlement -> SettlementReceipt. -## GTM -1. prove the workflow in one repository; -2. pilot with one foundation/ecosystem bounty operator; -3. after validation, expand repository support and integrate an approved settlement adapter. +The verifier checks repository identity, issue binding, contributor policy, base branch, merged state, head SHA, merge commit SHA, and evidence integrity before the workflow advances. -No paid pilot or customer traction is claimed today. +## Technical differentiation -## Validation / evidence -Use only evidence captured for the exact submission commit: -- real GitHub issue and merged PR fixture; -- green Node test suite; -- green Daml build/script tests; -- real Canton lifecycle to SettlementReceipt; -- exact negative rejection evidence; -- browser-accessible judge flow and exported proof. +Canton/Daml currently provides: +- role-separated Maintainer, Contributor and Verifier authority; +- explicit allowed contract transitions; +- inspectable contract/update identifiers; +- replay protection through contract consumption; +- auditable SettlementReceipt generation. -Transport mocks are never described as live Canton evidence. +GitHub remains the external work-evidence source. -## Demo and pitch -The final recorded demo must be **5 minutes or less** and show: -1. problem and roles; -2. real GitHub issue/PR evidence; -3. authorized Canton lifecycle; -4. wrong-evidence / unauthorized / replay rejection; -5. final receipt and why Canton is necessary; -6. Track 1 business use and pilot path. +## Verified technical evidence + +Use the frozen proven technical commit: + +`26bd992787401f6458f6685d2ab76aacd05eab4e` + +Live proof: + +https://commitledger-proof-final.onrender.com + +Proof JSON: + +https://commitledger-proof-final.onrender.com/api/proof + +External demo evidence fixture: +- repository: `Saidur-droid/MergeEarn`; +- open issue: `#69`; +- merged PR: `#73`; +- contributor: `Saidur-droid`; +- base branch: `main`. + +**The MergeEarn repository is only a proof fixture. The submitted product is CommitLedger.** ## Integrity -DEMO_CREDIT is non-production test value. CommitLedger does not claim fiat/Canton Coin transfer, MainNet operation, production custody, real users, revenue, or organizer endorsement without evidence. -## AI disclosure -AI-assisted tools were used for research, planning, code review, tests, documentation, and implementation support. The team remains responsible for every submitted artifact and claim. +`DEMO_CREDIT` is test value only. + +Do not claim: +- fiat transfer; +- Canton Coin transfer; +- MainNet; +- production custody; +- real financial settlement; +- paying customers; +- revenue; +- organizer endorsement. + +AI-assisted tools were used for research, planning, code review, testing, documentation, and implementation support. The participant remains responsible for all submitted claims and artifacts. + +--- + +# Colosseum version + +## Positioning + +CommitLedger is **verifiable work-to-settlement infrastructure for open-source ecosystems and contributor programs**. -## Hackathon-period disclosure -Any pre-existing code must be disclosed. Only work completed during the official delivery phase (2026-09-18 through 2026-10-09) should be presented to judges as hackathon-period work. +## Colosseum story + +Emphasize: +- founder/market insight; +- why contributor settlement is still operationally fragmented; +- why evidence-linked authorization matters; +- product execution and security proof; +- target user; +- market opportunity; +- demand validation; +- go-to-market; +- startup viability. + +Avoid presenting CommitLedger merely as a hackathon technical demo. + +## Work still needed + +- official live-rule recheck; +- registration confirmation; +- competition-window/pre-existing-work disclosure; +- user/demand validation; +- final GTM; +- presentation video; +- product demo; +- final portal fields; +- submission receipt. + +--- + +# Ideathon Bangladesh 2026 version + +## Positioning + +CommitLedger is an auditable work-verification and settlement-control product for organizations coordinating distributed contributor work. + +## Ideathon story + +Emphasize: +- business problem; +- target customers; +- operational trust/audit value; +- remote software-work and ecosystem-program use cases; +- Bangladesh-relevant opportunity only where evidence supports it; +- business model hypothesis; +- adoption path; +- team execution; +- technical credibility from the verified MVP. + +Do not invent local customers or traction. + +## Work still needed + +- official live-rule recheck; +- registration/application confirmation; +- application fields; +- business model; +- market sizing; +- validation evidence; +- pitch materials; +- final submission; +- submission receipt. ## Final links -Populate only when public and verified: + +Populate only after each program's rules are re-verified: - Repository: -- Live/demo page: -- Demo video: -- Pitch: -- Evidence bundle: +- Live product/proof: https://commitledger-proof-final.onrender.com +- Proof JSON: https://commitledger-proof-final.onrender.com/api/proof +- Colosseum presentation: +- Colosseum demo: +- Colosseum submission receipt: +- Ideathon pitch/application: +- Ideathon submission receipt: From c1705e8b2c77fee21436dd414b59c45e5b5b0e21 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 7 Oct 2026 10:50:55 +0600 Subject: [PATCH 135/144] docs: refresh README.md for Oct 7 dual submission sprint --- README.md | 95 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 55 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 13c960f..afb66c7 100644 --- a/README.md +++ b/README.md @@ -2,52 +2,67 @@ **Canton-native settlement infrastructure for verified open-source work.** -CommitLedger binds real contribution evidence to role-separated authorization and an auditable settlement receipt. +CommitLedger binds canonical contribution evidence to role-separated authorization and an auditable settlement receipt. -## Current competition targets +## Active competition targets — 7 October 2026 -CommitLedger is currently being prepared for exactly two programs: +This sprint has exactly two active submission targets: -1. **Crypto World's Fair — Colosseum** -2. **Ideathon Bangladesh 2026** +1. **HackCanton Season 3** + - Submission deadline: **9 Oct 2026, 23:59 UTC** + - Bangladesh time: **10 Oct 2026, 05:59** +2. **Crypto World's Fair — Colosseum** + - Contest/submission cutoff: **12 Oct 2026, 23:59 PT** + - Bangladesh time: **13 Oct 2026, 12:59** -See [Competition Targets](docs/COMPETITION_TARGETS.md) for the canonical plan, eligibility-first rules, and next-session order. +The 1 October plan that retired HackCanton is superseded by the owner's 7 October instruction to submit CommitLedger to both programs. -HackCanton Season 3 is no longer an active submission target. Its technical work is preserved as product proof/history. +See [Competition Targets](docs/COMPETITION_TARGETS.md), [Submission Status](docs/SUBMISSION_STATUS_20261007.md), [Final Checklist](docs/FINAL_CHECKLIST.md), and [Submission Draft](docs/SUBMISSION_DRAFT.md). ## Product thesis `GitHub Issue -> Bounty -> Claim -> Pull Request -> Merge Evidence -> Canton Settlement -> SettlementReceipt` -Canton is the current trust and settlement layer, not a cosmetic integration. Removing the ledger authorization layer removes the product's role controls, provenance, replay protection and final settlement record. +Canton/Daml is the authorization and workflow-state layer, not a cosmetic integration. GitHub remains the canonical external work-evidence source. ## Core guarantees - **Maintainer** creates the bounty, accepts the claim and settles verified work. -- **Contributor** creates the claim and submits the exact PR. -- **Verifier** can attest only canonical merge evidence. -- Merge evidence includes the exact bounty issue number. -- Wrong issue, wrong SHA, unmerged work and unauthorized actions are rejected. -- Settlement consumes the verified contract, blocking duplicate settlement. +- **Contributor** claims work and submits the exact PR. +- **Verifier** attests canonical merge evidence. +- Merge evidence is bound to the exact issue and PR metadata. +- Wrong evidence, unauthorized actions and replay/duplicate settlement are rejected. +- Settlement consumes the verified contract and creates an auditable `SettlementReceipt`. - `DEMO_CREDIT` is explicitly non-production test value. -## Verified proof fixture — not the product repository +## Frozen technical proof -The current reproducible external evidence fixture is: +The last fully executed, source-bound technical proof is: + +`26bd992787401f6458f6685d2ab76aacd05eab4e` + +Verified at that exact code commit: + +- Node tests: **86/86 PASS** +- DPM **3.5.12** PASS +- Daml build PASS +- Daml Script tests PASS +- fresh Canton sandbox PASS +- six ledger transitions PASS +- `SettlementReceipt` generated +- wrong-evidence rejection captured +- unauthorized-settlement rejection captured +- duplicate/replay rejection captured + +External GitHub evidence fixture: - Repository: `Saidur-droid/MergeEarn` - Open issue: `#69` - Merged PR: `#73` -- Contributor: `Saidur-droid` -- Base branch: `main` - -**CommitLedger is the product. MergeEarn is only the external GitHub issue/PR evidence source used to prove that CommitLedger can verify and settle work from another repository.** -Frozen fully executed technical proof commit: +**MergeEarn is only the external proof fixture. CommitLedger is the product being submitted.** -`26bd992787401f6458f6685d2ab76aacd05eab4e` - -Live proof: +Canonical proof service: https://commitledger-proof-final.onrender.com @@ -55,6 +70,8 @@ Proof JSON: https://commitledger-proof-final.onrender.com/api/proof +Planning/documentation commits after the frozen proof do not become runtime-proven automatically. If executable code changes, rerun the complete proof on the exact new commit. + ## Run locally Requires Node.js 22+. @@ -66,8 +83,6 @@ npm start Open `http://127.0.0.1:4173`. -Because this repository may be private during preparation, set `GITHUB_TOKEN` with the required read access before using the canonical GitHub verifier locally. - ## Run the full Canton proof Configure the documented Canton environment values, then run: @@ -76,26 +91,26 @@ Configure the documented Canton environment values, then run: bash scripts/run-local-proof.sh ``` -The proof path reruns tests, builds/tests Daml, starts a fresh Canton sandbox, captures each ledger transition, executes negative authorization/replay checks, validates one coherent evidence bundle, and finishes at SettlementReceipt. - -## Canton / Daml - -The Daml project is under `daml/` and uses the pinned open-source DPM SDK bundle **3.5.12**. +The proof path reruns tests, builds/tests Daml, starts a fresh Canton sandbox, captures each ledger transition, executes negative authorization/replay checks, validates one coherent evidence bundle, and finishes at `SettlementReceipt`. -## Planning and governance +## Submission assets -- [Competition Targets](docs/COMPETITION_TARGETS.md) -- [Next Session Handoff](docs/NEXT_SESSION_HANDOFF.md) -- [Dual-Program Final Checklist](docs/FINAL_CHECKLIST.md) -- [Dual-Program Submission Master Draft](docs/SUBMISSION_DRAFT.md) -- [Delivery Status](docs/DELIVERY_STATUS.md) -- [Architecture](docs/ARCHITECTURE.md) -- [Threat Model](docs/THREAT_MODEL.md) -- [Evidence Manifest](docs/EVIDENCE_MANIFEST.md) +- [Rules recheck — 7 Oct](docs/RULES_RECHECK_20261007.md) +- [Submission status — 7 Oct](docs/SUBMISSION_STATUS_20261007.md) +- [Competition targets](docs/COMPETITION_TARGETS.md) +- [Final checklist](docs/FINAL_CHECKLIST.md) +- [Submission master draft](docs/SUBMISSION_DRAFT.md) +- [HackCanton pitch](docs/PITCH.md) +- [HackCanton demo script](docs/DEMO_SCRIPT.md) +- [Colosseum pitch script](docs/COLOSSEUM_PITCH_SCRIPT.md) +- [Colosseum demo script](docs/COLOSSEUM_DEMO_SCRIPT.md) +- [Hackathon work disclosure](docs/HACKATHON_WORK_DISCLOSURE.md) +- [AI assistance disclosure](docs/AI_DISCLOSURE.md) +- [Delivery status](docs/DELIVERY_STATUS.md) ## Integrity -CommitLedger does not claim Canton Coin transfer, fiat settlement, external adoption, mainnet usage, revenue, paying customers, or production custody without evidence. +CommitLedger does not claim Canton Coin transfer, fiat settlement, external adoption, MainNet usage, revenue, paying customers, or production custody without evidence. `DEMO_CREDIT` is test value only. From cbcf895ae7a58b5dcbbe0c17154d0ef78b3add10 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 7 Oct 2026 10:50:57 +0600 Subject: [PATCH 136/144] docs: refresh docs/COMPETITION_TARGETS.md for Oct 7 dual submission sprint --- docs/COMPETITION_TARGETS.md | 226 +++++++++++++++++++----------------- 1 file changed, 120 insertions(+), 106 deletions(-) diff --git a/docs/COMPETITION_TARGETS.md b/docs/COMPETITION_TARGETS.md index a8856d8..a9a5cfc 100644 --- a/docs/COMPETITION_TARGETS.md +++ b/docs/COMPETITION_TARGETS.md @@ -1,27 +1,19 @@ -# Competition Targets — 1 October 2026 +# Competition Targets — 7 October 2026 -**This is the canonical competition plan for CommitLedger.** +**Canonical plan for the current CommitLedger sprint.** -## Active submission targets +The owner's 7 October instruction supersedes the 1 October planning note that had retired HackCanton. -CommitLedger will now be prepared for exactly these two programs: +## Active targets -1. **Crypto World's Fair — Colosseum** -2. **Ideathon Bangladesh 2026** +1. **HackCanton Season 3** +2. **Crypto World's Fair — Colosseum** -These are the active targets. If a future session asks, “Which programs are we participating in?”, answer with these two unless this file is deliberately updated after a fresh rules/eligibility review. +Ideathon Bangladesh 2026 is not part of this immediate CommitLedger submission sprint. -## Retired target +## Frozen technical baseline -HackCanton Season 3 is **not an active submission target anymore** because the required AppsFactory account-registration/activity gates were not completed in time. - -Do not spend more time on HackCanton submission work. - -The Canton/Daml implementation and verified proof created during that effort remain valuable technical evidence and are preserved. Do not delete or rewrite that history. - -## Frozen technical proof - -The last fully executed, source-bound technical proof is frozen at: +Last fully executed, source-bound technical proof: `26bd992787401f6458f6685d2ab76aacd05eab4e` @@ -33,135 +25,157 @@ Proof endpoint: https://commitledger-proof-final.onrender.com/api/proof -That proof includes: +Verified on the frozen code commit: - Node 86/86 PASS; -- DPM 3.5.12; -- Daml build and Daml Script PASS; -- a fresh Canton sandbox; -- six real ledger transitions; +- DPM 3.5.12 PASS; +- Daml build PASS; +- Daml Script tests PASS; +- fresh Canton sandbox; +- six ledger transitions; - SettlementReceipt; -- wrong-issue rejection; +- wrong-evidence rejection; - unauthorized-settlement rejection; - duplicate/replay rejection; - exact source-commit binding. -Documentation-only commits made after the frozen proof do **not** become runtime-proven commits automatically. If executable code changes later, rerun the full proof on the new exact code commit. +Planning/documentation commits after the frozen proof are not automatically runtime-proven. If executable code changes, rerun the full proof. -## Global rule before any new competition work - -**Eligibility first. Build second.** +--- -Before changing code for any event, verify and record: -1. registration is still open; -2. the owner is eligible by age/country/team rules; -3. participation and submission do not require a paid product/service; -4. pre-existing projects/code are allowed or can be disclosed; -5. required build window and judging scope; -6. required chain/stack/track; -7. submission deadline and mandatory deliverables. +# Target 1 — HackCanton Season 3 -If any mandatory gate is unknown, do not start engineering until it is resolved. +## Live-verified timing -**Owner constraint:** use free options only. Do not introduce paid tools/services unless the owner explicitly changes this rule. +Current public schedule: +- build phase: 18 Sep–9 Oct 2026; +- submission deadline: **9 Oct 2026, 23:59 UTC**; +- Bangladesh equivalent: **10 Oct 2026, 05:59**; +- judging: 10–18 Oct; +- finalists: 19 Oct; +- Grand Final: 21 Oct, 14:00 UTC. ---- +Primary sources/recheck pointers: +- https://appsfactory.cc/hackathons +- https://forum.canton.network/ +- current ecosystem schedule/article notes captured in `RULES_RECHECK_20261007.md`. -# Target 1 — Crypto World's Fair — Colosseum +## Fit -## Current plan +CommitLedger is a strong Canton-native business-workflow submission because Daml/Canton controls the core authorization and settlement state: -Treat CommitLedger as a startup/product submission, not as a HackCanton project. +`Issue -> Bounty -> Claim -> PR -> Verify -> Settle -> SettlementReceipt` -Core product thesis: +Primary track candidate: **Real-World Asset & Business Workflows** because the product is an end-to-end role-authorized business workflow. Verify the actual portal track names before final selection. -> CommitLedger is verifiable work-to-settlement infrastructure for open-source ecosystems and contributor programs. It turns canonical contribution evidence into an authorization-controlled settlement workflow with replay protection and an auditable receipt. +## Technical status -## What should stay +**GREEN.** Product engineering and runtime proof are complete at the frozen proof commit. -Keep the proven product core: -- GitHub evidence verification; -- role-separated authorization; -- Canton/Daml state machine; -- replay protection; -- SettlementReceipt; -- structured negative/security proof; -- public read-only proof page. +## Submission-side status -Do **not** rewrite the core merely to look different for Colosseum. +Ready now: +- product; +- runtime proof; +- security/negative proof; +- architecture/business brief; +- pitch text; +- demo storyboard; +- AI disclosure; +- hackathon-work disclosure draft. -## What should improve for Colosseum +Still external/manual: +- AppsFactory login/registration state; +- team/project profile state; +- actual portal-required track and fields; +- recorded final video; +- repository judge access/public visibility; +- final portal submission; +- submission receipt. -Primary work should be submission/business quality: -- founder/market framing; -- target-user clarity; -- credible market size; -- demand/user validation; -- go-to-market plan; -- concise presentation video; -- concise product demo; -- transparent pre-existing-work disclosure; -- clear explanation of what was built during the competition window. +### Historical AppsFactory gate warning -Any competition-specific code change must be made on a separate branch and only after official rules are re-verified. +This repository previously modeled external gates named: +- `mana1000`; +- `activity10Days`; +- `journalNonEmpty`; +- `projectProfileComplete`; +- `track1Selected`. -## Current deadline note +These are **not being promoted here as universal public rules** because the live public site does not expose the full private participant checklist. Inspect the logged-in AppsFactory portal immediately. If any of these are mandatory and already unmet, do not misrepresent eligibility. -Current research recorded a submission deadline of **12 October 2026**. +--- -Before registration or final submission, re-open the official Colosseum rules and verify the live deadline, eligibility, deliverables, and pre-existing-code rules again. Do not rely on this repository note as a substitute for the live rules. +# Target 2 — Crypto World's Fair — Colosseum ---- +## Official timing -# Target 2 — Ideathon Bangladesh 2026 +Official rules state: +- contest starts: **14 Sep 2026, 6:00 AM PT**; +- contest ends / registration + submission cutoff: **12 Oct 2026, 11:59 PM PT**; +- Bangladesh equivalent: **13 Oct 2026, 12:59**; +- winners announced by 5 Dec 2026. -## Current plan +Official page: +https://colosseum.com/worldsfair -Use the same CommitLedger core, but position it around business value and a credible adoption path. +Official rules: +https://colosseum.com/legal/Crypto%20World%27s%20Fair%20Hackathon%20Rules.pdf -Suggested business framing: -- auditable contributor/work settlement; -- open-source and ecosystem bounty operations; -- remote software-work verification; -- organizations that need evidence-linked authorization before settlement. +## Eligibility/rules currently verified -Do not fabricate Bangladeshi customers, revenue, pilots, or adoption. +- no purchase necessary; +- age of majority in country/residence or at least 18, whichever is older; +- Bangladesh is not in the current listed excluded jurisdictions; +- every member must register before the cutoff; +- one entrant may be on only one team; +- a team may submit one project at a time; +- all submitted content must be in English; +- judging includes functionality, potential impact, novelty, UX, open-source/composability, and business plan. -## What should stay +Current Colosseum FAQ also states: +- pre-existing code is allowed; +- previous development must be disclosed; +- judging focuses on work completed during the contest; +- repository link is required (private allowed if review access is granted); +- presentation video is requested; +- product demo video is requested; +- GTM, demand validation and distribution plan are requested. -No core-code rewrite is currently planned. +## Fit -Use the existing technical proof as product credibility, then strengthen: -- problem statement; -- customer segment; -- Bangladesh-relevant business use cases where justified; -- go-to-market; -- pricing/business model hypothesis; -- validation interviews/feedback; -- pitch clarity. +Submit CommitLedger as **verifiable work-to-settlement infrastructure for open-source ecosystems and contributor programs**. -## Current deadline note +Do not force an ecosystem-track claim unless CommitLedger genuinely integrates the required chain. The general competition remains available across ecosystems. -Current research recorded a Round 1 deadline of **20 October 2026 at 11:59 PM Bangladesh time**. +## Technical status -Before applying or submitting, re-verify the official live rules, deadline, team/eligibility requirements, and application fields. +**GREEN.** Reuse the frozen proven product; do not rewrite core code just to make it look new. ---- +## Competition-specific work still needed -# Next-session order +- Colosseum account/join confirmation; +- project record in portal; +- transparent pre-existing-work disclosure; +- concise founder/market narrative; +- demand validation that is real, not invented; +- GTM/business model; +- 2–3 minute presentation video; +- <=3 minute product demo video; +- repository review access; +- final submission + receipt. -When work resumes: +--- -1. Read this file first. -2. Re-verify **Colosseum** live rules and registration status. -3. Register/apply first and retain confirmation evidence. -4. Re-verify **Ideathon Bangladesh 2026** live rules and registration status. -5. Register/apply first and retain confirmation evidence. -6. Freeze the competition-specific requirements in repo docs. -7. Build the Colosseum submission package. -8. Build the Ideathon submission package. -9. Only make product/code changes if a verified competition requirement or concrete product defect requires them. -10. Never claim a submission is complete until the relevant portal provides confirmation/receipt. +# Execution order — 7 October -## Not doing today +1. **HackCanton portal check first** because its hard deadline is earlier. +2. Resolve registration/profile/eligibility gates. +3. Record and upload HackCanton final video. +4. Ensure judge-accessible repo + proof links. +5. Submit HackCanton and save receipt. +6. Then complete Colosseum registration/project fields. +7. Record Colosseum presentation + demo videos. +8. Finalize pre-existing-work disclosure, GTM, validation and business model. +9. Submit Colosseum and save receipt. -No product engineering, video production, portal submission, or competition-specific feature work is required on 1 October 2026 after this planning update. +Owner constraint: use free tools/services unless explicitly changed. From b63b19b818e6284b4f6a3630f23faeae8a56bba6 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 7 Oct 2026 10:51:00 +0600 Subject: [PATCH 137/144] docs: refresh docs/FINAL_CHECKLIST.md for Oct 7 dual submission sprint --- docs/FINAL_CHECKLIST.md | 156 +++++++++++++++++++++------------------- 1 file changed, 83 insertions(+), 73 deletions(-) diff --git a/docs/FINAL_CHECKLIST.md b/docs/FINAL_CHECKLIST.md index 3a5a4b4..4b8c23e 100644 --- a/docs/FINAL_CHECKLIST.md +++ b/docs/FINAL_CHECKLIST.md @@ -1,89 +1,99 @@ -# CommitLedger — Dual-Program Final Checklist +# CommitLedger — HackCanton + Colosseum Final Checklist -Canonical active targets: -1. **Crypto World's Fair — Colosseum** -2. **Ideathon Bangladesh 2026** +_Last updated: 7 October 2026._ -Do not use the old HackCanton/AppsFactory checklist as the active plan. - -## Frozen technical baseline +## Shared frozen technical baseline - [x] Product core implemented. -- [x] Node tests: 86/86 PASS on proven build. +- [x] Canonical GitHub evidence verification implemented. +- [x] Role-separated Maintainer / Contributor / Verifier workflow implemented. +- [x] Node tests: 86/86 PASS on frozen proof commit. - [x] DPM 3.5.12 PASS. - [x] Daml build PASS. - [x] Daml Script tests PASS. - [x] Fresh Canton sandbox proof completed. - [x] Six-step ledger lifecycle completed. - [x] SettlementReceipt generated. -- [x] Wrong-issue rejection captured. +- [x] Wrong-evidence rejection captured. - [x] Unauthorized-settlement rejection captured. - [x] Duplicate/replay rejection captured. -- [x] Public read-only proof endpoint exists. -- [x] Frozen technical proof commit: `26bd992787401f6458f6685d2ab76aacd05eab4e`. -- [x] Canonical Render service: `commitledger-proof-final`. - -Documentation/planning commits after the proven commit are not automatically runtime-proven. - -## Universal eligibility gate — must pass before coding - -For each target: -- [ ] Registration is still open. -- [ ] Participant/team eligibility confirmed. -- [ ] Free participation path confirmed. -- [ ] Pre-existing-code/project rule confirmed. -- [ ] Competition build/judging window confirmed. -- [ ] Required chain/stack/track confirmed. -- [ ] Required deliverables confirmed. -- [ ] Deadline confirmed from live official source. -- [ ] Registration/application confirmation saved. - -If any item above is unknown, do not start competition-specific engineering. - -## Colosseum checklist - -- [ ] Live rules re-verified. -- [ ] Registration completed. -- [ ] Confirmation/receipt retained. -- [ ] Pre-existing-work disclosure prepared. -- [ ] Competition-window work identified from Git history. -- [ ] Founder/market positioning finalized. -- [ ] Target-user validation collected. -- [ ] Market/GTM story finalized. -- [ ] Presentation video recorded and reviewed. -- [ ] Product demo recorded and reviewed. -- [ ] Repository/access requirement satisfied. -- [ ] Every submission link tested logged-out where applicable. +- [x] Public read-only proof service recorded. +- [x] Frozen proof commit: `26bd992787401f6458f6685d2ab76aacd05eab4e`. +- [x] AI assistance disclosure exists. +- [x] Hackathon work disclosure exists. +- [x] HackCanton pitch copy exists. +- [x] HackCanton demo script exists. +- [x] Colosseum pitch script exists. +- [x] Colosseum demo script exists. + +If executable code changes, rerun the complete proof on the exact new code commit. + +## HackCanton Season 3 + +Deadline: **9 Oct 2026, 23:59 UTC = 10 Oct 2026, 05:59 Bangladesh time**. + +### Rules / eligibility +- [x] Public deadline rechecked on 7 Oct. +- [x] Solo/team format publicly supported. +- [x] Canton-native fit documented. +- [ ] Logged-in AppsFactory registration state confirmed. +- [ ] Team/project profile completeness confirmed. +- [ ] Exact portal track selected and confirmed. +- [ ] Any private portal activity/Mana/journal requirements checked. +- [ ] Eligibility status confirmed from the participant portal. + +### Submission package +- [x] Product description drafted. +- [x] Canton integration explanation drafted. +- [x] Architecture/security evidence ready. +- [x] Demo storyboard ready. +- [x] <=5-minute target demo script ready. +- [ ] Final HackCanton video recorded. +- [ ] Video reviewed for secrets, legibility and audio. +- [ ] Repository accessible to judges. +- [ ] Live proof link tested in logged-out/private browser. +- [ ] Proof JSON link tested in logged-out/private browser. +- [ ] Final portal fields completed. +- [ ] Final Submit action completed. +- [ ] Submission receipt / confirmation retained. + +**Stop condition:** if the logged-in portal shows a mandatory gate that cannot be truthfully satisfied, do not fake it. Preserve the technical package and submit only if the official portal allows an eligible entry. + +## Crypto World's Fair — Colosseum + +Official cutoff: **12 Oct 2026, 11:59 PM PT = 13 Oct 2026, 12:59 Bangladesh time**. + +### Eligibility / registration +- [x] Official rules PDF rechecked on 7 Oct. +- [x] No-purchase requirement confirmed. +- [x] Age rule captured. +- [x] Bangladesh not listed among current excluded jurisdictions. +- [x] English-content requirement captured. +- [x] Pre-existing-code disclosure rule captured from current FAQ. +- [ ] Colosseum account/login confirmed. +- [ ] Crypto World's Fair joined. +- [ ] Team status confirmed. +- [ ] Project record created in portal. + +### Submission package +- [x] Product positioning drafted. +- [x] Technical proof ready. +- [x] Honest pre-existing-work disclosure draft ready. +- [x] Founder/market story draft ready. +- [x] Business model/GTM draft ready. +- [x] Presentation script ready. +- [x] Product demo script ready. +- [ ] Real demand/user validation added if available. +- [ ] 2–3 minute presentation video recorded. +- [ ] <=3 minute demo video recorded. +- [ ] Logo/graphic selected. +- [ ] Repository accessible to judges or review access granted. +- [ ] All content checked to be English. +- [ ] Final links tested logged-out where applicable. - [ ] Final portal review completed. -- [ ] Submission completed. +- [ ] Final Submit action completed. - [ ] Submission receipt retained. -## Ideathon Bangladesh 2026 checklist - -- [ ] Live rules re-verified. -- [ ] Registration/application completed. -- [ ] Confirmation retained. -- [ ] Problem and customer segment finalized. -- [ ] Bangladesh relevance stated only with defensible evidence. -- [ ] Business model hypothesis finalized. -- [ ] Market sizing prepared. -- [ ] Validation evidence collected. -- [ ] Pitch/application materials completed. -- [ ] Required links/files tested. -- [ ] Final application review completed. -- [ ] Submission completed. -- [ ] Submission receipt retained. - -## Code-change rule - -Do not change product code merely to “refresh” the project for a new competition. - -Only change executable code when: -- a verified competition requirement requires it; or -- a real product/QA defect is found. - -If executable code changes, rerun the complete Render proof and bind evidence to the exact new code commit. - -## Owner constraint +## Release rule -Use free tools/services only unless the owner explicitly approves otherwise. +Do not claim either competition is submitted until the portal itself returns a confirmation/receipt. From 21669c0115d309d244da0140dc56563d633962c1 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 7 Oct 2026 10:51:03 +0600 Subject: [PATCH 138/144] docs: refresh docs/NEXT_SESSION_HANDOFF.md for Oct 7 dual submission sprint --- docs/NEXT_SESSION_HANDOFF.md | 102 ++++++++++++++++------------------- 1 file changed, 47 insertions(+), 55 deletions(-) diff --git a/docs/NEXT_SESSION_HANDOFF.md b/docs/NEXT_SESSION_HANDOFF.md index 71ca60d..b5385bb 100644 --- a/docs/NEXT_SESSION_HANDOFF.md +++ b/docs/NEXT_SESSION_HANDOFF.md @@ -1,86 +1,78 @@ # NEXT SESSION HANDOFF — CommitLedger -**Read this first. Do not ask the owner to repeat the project history.** +**Current date context: 7 October 2026.** -## Active competition plan +Do not ask the owner to repeat project history. Read this file, `SUBMISSION_STATUS_20261007.md`, and `FINAL_CHECKLIST.md` first. -CommitLedger now has exactly two active submission targets: +## Active targets -1. **Crypto World's Fair — Colosseum** -2. **Ideathon Bangladesh 2026** +1. **HackCanton Season 3** +2. **Crypto World's Fair — Colosseum** -Canonical plan: [COMPETITION_TARGETS.md](COMPETITION_TARGETS.md) +The previous 1 October note that retired HackCanton is superseded by the owner's 7 October instruction. -HackCanton Season 3 is no longer an active submission target. Do not resume AppsFactory/Mana/journal work. +## Frozen technical proof -## Technical product status - -Product engineering is complete unless a new concrete defect or a verified new-competition requirement appears. - -Frozen fully proven technical commit: +Last fully executed, source-bound technical proof: `26bd992787401f6458f6685d2ab76aacd05eab4e` -Canonical Render service: - -`commitledger-proof-final` - -Live proof page: - +Canonical proof service: https://commitledger-proof-final.onrender.com Proof endpoint: - https://commitledger-proof-final.onrender.com/api/proof -Successful proven deploy: - -`dep-daujs4m0tbcc73dn0c80` - -Verified proof: -- Node 86/86 PASS; -- DPM 3.5.12 PASS; -- Daml build PASS; -- Daml Script tests PASS; +Verified: +- Node 86/86; +- DPM 3.5.12; +- Daml build/tests; - fresh Canton sandbox; -- MergeEarn #69 -> PR #73 as the external GitHub evidence fixture; -- six real Canton transitions; +- six ledger transitions; - SettlementReceipt; -- three structured negative/rejection proofs; +- three negative/rejection proofs; - exact source-commit binding. -**Important:** MergeEarn is only the external GitHub evidence fixture. CommitLedger is the product. +**MergeEarn is only the external GitHub evidence fixture. CommitLedger is the product.** -## Branch/proof rule +## Deadline order -Planning/documentation commits may be newer than the frozen technical proof. +### HackCanton +Hard deadline: **9 Oct 23:59 UTC / 10 Oct 05:59 Bangladesh time**. -Do not call a newer documentation commit “runtime proven.” +### Colosseum +Hard deadline: **12 Oct 23:59 PT / 13 Oct 12:59 Bangladesh time**. -If executable code changes, rerun the full Render proof on the exact new code commit before using it as technical evidence. +## Exact next actions -## Global competition rule +### First: HackCanton portal +1. Log in to AppsFactory. +2. Confirm registration/eligibility/project/team state. +3. Inspect the exact remaining mandatory gates. +4. Confirm track. +5. Record the final <=5-minute video using `DEMO_SCRIPT.md`. +6. Ensure repo + proof links are judge-accessible. +7. Complete portal fields. +8. Submit and save the confirmation/receipt. -Before engineering for either target, verify: -- registration open; -- participant/team eligibility; -- no paid-service requirement; -- pre-existing-code policy; -- judging/build window; -- required stack/track; -- submission deadline and deliverables. +Do not assume historical external gate names (Mana/activity/journal/profile) are universal rules; verify them in the logged-in portal. If a mandatory gate is unmet, do not fabricate compliance. -Registration/eligibility comes before feature work. +### Second: Colosseum +1. Log in/register and join Crypto World's Fair. +2. Create/confirm the CommitLedger project entry. +3. Use the prepared submission copy in `SUBMISSION_DRAFT.md`. +4. Record `COLOSSEUM_PITCH_SCRIPT.md` and `COLOSSEUM_DEMO_SCRIPT.md`. +5. Add only real demand validation. +6. Disclose pre-existing work honestly. +7. Give judges repo access. +8. Submit and save the receipt. -## Exact next action +## Code-change rule -Next session begins with **Colosseum registration/rules verification**, not coding. +Do not change executable code unless: +- a verified competition requirement requires it; or +- a real product defect is found. -Then: -1. register/apply to Colosseum and retain confirmation; -2. verify/register for Ideathon Bangladesh 2026 and retain confirmation; -3. write the final requirements matrix; -4. prepare competition-specific pitch/demo/submission materials; -5. only then consider any necessary changes. +If executable code changes, rerun the entire proof and bind the evidence to the exact new commit. -Owner constraint: **free tools/services only** unless explicitly changed. +Owner constraint: free tools/services only unless explicitly changed. From 2d29e21a5ceb31d8b0fca4a11656fc71e4284d99 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 7 Oct 2026 10:51:05 +0600 Subject: [PATCH 139/144] docs: refresh docs/DELIVERY_STATUS.md for Oct 7 dual submission sprint --- docs/DELIVERY_STATUS.md | 117 +++++++++++++++++++--------------------- 1 file changed, 56 insertions(+), 61 deletions(-) diff --git a/docs/DELIVERY_STATUS.md b/docs/DELIVERY_STATUS.md index e96bc9f..b1bfd30 100644 --- a/docs/DELIVERY_STATUS.md +++ b/docs/DELIVERY_STATUS.md @@ -1,84 +1,79 @@ -# Delivery status — 1 October 2026 +# Delivery Status — 7 October 2026 Delivery branch: `fix/verified-delivery-20260929`. -## Product/technical status +## Product / technical status -The CommitLedger product core is complete. +**Technical delivery is GREEN.** -The last fully executed, source-bound technical proof is: +Frozen fully executed proof commit: `26bd992787401f6458f6685d2ab76aacd05eab4e` -Canonical Render service: +Canonical Render proof service: `commitledger-proof-final` Live page: - https://commitledger-proof-final.onrender.com Proof endpoint: - https://commitledger-proof-final.onrender.com/api/proof -Successful proven deploy: - +Successful proven deploy recorded in project history: `dep-daujs4m0tbcc73dn0c80` -PR #8 was confirmed open, non-draft, ready for review and mergeable at the proven technical state. - -## Verified proof - -- Node 86/86 PASS. -- DPM 3.5.12 PASS. -- Daml build PASS. -- Daml Script tests PASS. -- Java 21 runtime works. -- Fresh Canton sandbox works. -- DAR package identity extracted canonically. -- Three distinct demo parties allocated. -- External GitHub proof fixture: `Saidur-droid/MergeEarn#69` -> merged PR `#73`. -- Six real Canton transitions completed. -- SettlementReceipt generated. -- Wrong-issue evidence rejected. -- Unauthorized contributor settlement rejected. -- Duplicate/replay settlement rejected. -- Evidence source-commit bound. -- Public proof bundle available. - -**MergeEarn is only the external evidence fixture; CommitLedger is the product.** - -## Competition direction changed - -HackCanton Season 3 is no longer an active submission target because the required account-registration/activity gates were not completed in time. - -The active targets are now: - -1. **Crypto World's Fair — Colosseum** -2. **Ideathon Bangladesh 2026** - -Canonical competition plan: - -[COMPETITION_TARGETS.md](COMPETITION_TARGETS.md) +Verified on the frozen code state: +- Node 86/86 PASS; +- DPM 3.5.12 PASS; +- Daml build PASS; +- Daml Script tests PASS; +- Java 21 runtime; +- fresh Canton sandbox; +- DAR/package identity extraction; +- three distinct demo parties; +- external GitHub fixture `Saidur-droid/MergeEarn#69 -> PR #73`; +- six real Canton transitions; +- SettlementReceipt; +- wrong-evidence rejection; +- unauthorized-settlement rejection; +- duplicate/replay rejection; +- source-commit binding. + +## Active competition direction + +The current sprint has exactly two active targets: + +1. **HackCanton Season 3** +2. **Crypto World's Fair — Colosseum** + +The 1 October retirement of HackCanton is superseded by the owner's 7 October instruction. + +## Submission readiness + +### HackCanton +- technical product: DONE; +- evidence/security proof: DONE; +- written pitch/demo plan: DONE; +- rules deadline recheck: DONE; +- logged-in AppsFactory eligibility/profile state: **PENDING MANUAL CHECK**; +- recorded final video: PENDING; +- repo/publication gate: PENDING; +- final portal submission/receipt: PENDING. + +### Colosseum +- technical product: DONE; +- official rules recheck: DONE; +- pre-existing-work approach: DONE; +- submission copy: DRAFTED; +- presentation/demo scripts: DONE; +- portal registration/join state: **PENDING MANUAL CHECK**; +- real demand validation: PENDING if available; +- final videos: PENDING; +- final portal submission/receipt: PENDING. ## Engineering state -Do not reopen product engineering unless: -- a real QA defect appears; or -- a verified requirement from one of the two active competitions requires a change. - -Documentation-only planning commits may advance the branch without invalidating the statement that `26bd992...` is the frozen proven **code** state. Do not describe a later docs-only branch head as runtime-proven. - -If executable code changes, rerun the entire Render proof on the exact new code commit. - -## Next work - -Next session: -1. verify Colosseum live rules and registration; -2. register/apply and save confirmation; -3. verify Ideathon Bangladesh live rules and registration; -4. register/apply and save confirmation; -5. then prepare competition-specific business/pitch/demo materials. +Do not reopen product engineering unless a verified rule requires it or a real defect appears. -Owner constraint: free tools/services only. +Documentation-only commits may advance the branch without changing the frozen technical proof claim. If executable code changes, rerun the full proof on the exact new commit. From 2309cd717356fa47d5c75a0d640c6ffdc2022885 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 7 Oct 2026 10:51:07 +0600 Subject: [PATCH 140/144] docs: refresh docs/SUBMISSION_DRAFT.md for Oct 7 dual submission sprint --- docs/SUBMISSION_DRAFT.md | 244 ++++++++++++++++++++++++--------------- 1 file changed, 149 insertions(+), 95 deletions(-) diff --git a/docs/SUBMISSION_DRAFT.md b/docs/SUBMISSION_DRAFT.md index 9224a37..f4b21bd 100644 --- a/docs/SUBMISSION_DRAFT.md +++ b/docs/SUBMISSION_DRAFT.md @@ -1,14 +1,14 @@ -# CommitLedger — Dual-Program Submission Master Draft +# CommitLedger — Submission Master Draft -**Active targets** -1. Crypto World's Fair — Colosseum -2. Ideathon Bangladesh 2026 +_Updated 7 October 2026._ -See [COMPETITION_TARGETS.md](COMPETITION_TARGETS.md) for the canonical plan and eligibility-first rule. +Active targets: +1. HackCanton Season 3 +2. Crypto World's Fair — Colosseum ## Shared one-line description -CommitLedger turns verified contribution work into an authorization-controlled settlement workflow with an auditable receipt. +**CommitLedger turns verified contribution work into an authorization-controlled settlement workflow with an auditable receipt.** ## Shared problem @@ -16,47 +16,43 @@ A code host can prove that an issue exists and a pull request merged, but that e ## Shared solution -CommitLedger binds canonical contribution evidence to an authorization-controlled workflow: +CommitLedger binds canonical contribution evidence to a role-authorized workflow: -Issue -> Bounty -> Claim -> Pull Request -> Merge Verification -> Settlement -> SettlementReceipt. +`Issue -> Bounty -> Claim -> Pull Request -> Merge Verification -> Settlement -> SettlementReceipt` -The verifier checks repository identity, issue binding, contributor policy, base branch, merged state, head SHA, merge commit SHA, and evidence integrity before the workflow advances. +The verifier checks repository identity, issue binding, contributor policy, base branch, merged state, head SHA, merge commit SHA and evidence integrity before settlement can advance. ## Technical differentiation -Canton/Daml currently provides: +Canton/Daml provides: - role-separated Maintainer, Contributor and Verifier authority; - explicit allowed contract transitions; - inspectable contract/update identifiers; -- replay protection through contract consumption; +- consumed-contract replay protection; - auditable SettlementReceipt generation. GitHub remains the external work-evidence source. -## Verified technical evidence +## Verified evidence -Use the frozen proven technical commit: +Frozen proven technical commit: `26bd992787401f6458f6685d2ab76aacd05eab4e` Live proof: - https://commitledger-proof-final.onrender.com Proof JSON: - https://commitledger-proof-final.onrender.com/api/proof -External demo evidence fixture: -- repository: `Saidur-droid/MergeEarn`; +External evidence fixture: +- repo: `Saidur-droid/MergeEarn`; - open issue: `#69`; -- merged PR: `#73`; -- contributor: `Saidur-droid`; -- base branch: `main`. +- merged PR: `#73`. -**The MergeEarn repository is only a proof fixture. The submitted product is CommitLedger.** +**MergeEarn is only the proof fixture. CommitLedger is the submitted product.** -## Integrity +## Integrity statement `DEMO_CREDIT` is test value only. @@ -65,91 +61,149 @@ Do not claim: - Canton Coin transfer; - MainNet; - production custody; -- real financial settlement; -- paying customers; -- revenue; +- customers/revenue/traction that do not exist; - organizer endorsement. -AI-assisted tools were used for research, planning, code review, testing, documentation, and implementation support. The participant remains responsible for all submitted claims and artifacts. +AI-assisted tools were used for research, planning, code review, testing, documentation and implementation support. The participant remains responsible for all submitted claims and artifacts. --- -# Colosseum version +# HackCanton Season 3 + +## Suggested title + +**CommitLedger — Verifiable GitHub Work to Canton Settlement** + +## 15-second pitch + +CommitLedger turns verified GitHub contribution evidence into a role-authorized Canton workflow and an auditable SettlementReceipt. GitHub proves the work event; Daml controls who may verify and settle it; Canton preserves replay-safe workflow state. + +## Problem statement + +Open-source and contributor bounty workflows often stop at “the PR merged.” That proves a repository event, but not who is authorized to approve settlement, whether the evidence is bound to the correct issue, or whether the same work can be settled twice. + +## Solution + +CommitLedger adds an authorization-controlled settlement state machine: +1. maintainer creates a bounty for a canonical issue; +2. contributor claims it; +3. contributor submits exact PR metadata; +4. verifier checks canonical merged-PR evidence; +5. maintainer settles only verified work; +6. Canton creates the final SettlementReceipt. + +## Why Canton + +Without Canton/Daml, CommitLedger loses: +- explicit party authorization; +- contractual state transitions; +- consumed-contract replay protection; +- inspectable update/contract IDs; +- the final auditable receipt. + +## Track candidate -## Positioning +**Real-World Asset & Business Workflows** is the primary candidate because the product implements an end-to-end business workflow with role authorization and audit state. Confirm the exact portal option before selecting it. -CommitLedger is **verifiable work-to-settlement infrastructure for open-source ecosystems and contributor programs**. +## Demo evidence -## Colosseum story +Show: +- canonical GitHub issue + merged PR; +- Maintainer / Contributor / Verifier roles; +- six ledger transitions; +- SettlementReceipt; +- wrong-issue rejection; +- unauthorized settlement rejection; +- duplicate/replay rejection; +- exact source commit and proof bundle. -Emphasize: -- founder/market insight; -- why contributor settlement is still operationally fragmented; -- why evidence-linked authorization matters; -- product execution and security proof; -- target user; -- market opportunity; -- demand validation; -- go-to-market; -- startup viability. +## Repository / proof -Avoid presenting CommitLedger merely as a hackathon technical demo. +Repository: use the final judge-accessible CommitLedger repository URL. -## Work still needed +Live proof: +https://commitledger-proof-final.onrender.com -- official live-rule recheck; -- registration confirmation; -- competition-window/pre-existing-work disclosure; -- user/demand validation; -- final GTM; -- presentation video; -- product demo; -- final portal fields; -- submission receipt. +Proof JSON: +https://commitledger-proof-final.onrender.com/api/proof + +## Work-period disclosure + +CommitLedger includes pre-existing repository history/scaffolding. The qualifying HackCanton delivery phase added and hardened the Canton/Daml workflow, GitHub evidence binding, runtime proof automation, negative security proof, judge UI and competition materials. Reconcile the final wording with Git history before submission. --- -# Ideathon Bangladesh 2026 version - -## Positioning - -CommitLedger is an auditable work-verification and settlement-control product for organizations coordinating distributed contributor work. - -## Ideathon story - -Emphasize: -- business problem; -- target customers; -- operational trust/audit value; -- remote software-work and ecosystem-program use cases; -- Bangladesh-relevant opportunity only where evidence supports it; -- business model hypothesis; -- adoption path; -- team execution; -- technical credibility from the verified MVP. - -Do not invent local customers or traction. - -## Work still needed - -- official live-rule recheck; -- registration/application confirmation; -- application fields; -- business model; -- market sizing; -- validation evidence; -- pitch materials; -- final submission; -- submission receipt. - -## Final links - -Populate only after each program's rules are re-verified: -- Repository: -- Live product/proof: https://commitledger-proof-final.onrender.com -- Proof JSON: https://commitledger-proof-final.onrender.com/api/proof -- Colosseum presentation: -- Colosseum demo: -- Colosseum submission receipt: -- Ideathon pitch/application: -- Ideathon submission receipt: +# Crypto World's Fair — Colosseum + +## Project name + +**CommitLedger** + +## Public brief description + +CommitLedger is verifiable work-to-settlement infrastructure for open-source ecosystems and contributor programs. It binds canonical contribution evidence to role-authorized settlement state, blocks replay/duplicate settlement, and produces an auditable receipt. + +## What are you building, and who is it for? + +CommitLedger helps open-source maintainers, foundations, ecosystem programs and engineering organizations settle verified contributor work with explicit authorization and an audit trail. GitHub provides canonical issue/PR evidence. A verifier attests the evidence. Canton/Daml controls the allowed state transitions between maintainer, contributor and verifier and produces a replay-safe SettlementReceipt. + +## Why build this now? + +Contributor programs increasingly coordinate valuable work across public repositories, but the evidence, approval and settlement steps are fragmented across code hosts, spreadsheets, chats and payment tools. CommitLedger focuses on the control layer between “the work merged” and “the work is authorized for settlement,” making the decision inspectable, role-aware and resistant to duplicate settlement. + +## Technology + +- Daml / Canton for contractual workflow and authorization; +- Canton JSON Ledger API for ledger transitions and active-contract discovery; +- GitHub API for canonical issue and merged-PR evidence; +- Node.js backend/orchestration; +- browser judge UI; +- Render for the current read-only public proof service. + +AI-assisted tooling was used for research, planning, review, tests, documentation and implementation support. + +## Business model hypothesis + +CommitLedger would be sold to organizations operating contributor, bounty or ecosystem programs. A practical model is a SaaS/platform fee for workflow, policy and audit controls, with enterprise pricing for organization-level access, integrations and compliance features. Production asset/payment adapters would be added only after workflow validation. + +## Go-to-market + +1. Start with one open-source repository and a small maintainer-led bounty pilot. +2. Expand to a foundation/ecosystem contributor campaign. +3. Add organization controls, monitoring and policy integrations. +4. Add approved production settlement adapters after validation. + +## Demand validation + +Current proof is technical, not commercial traction. The project has a real end-to-end GitHub/Canton evidence fixture and executed runtime/security proof. Do not invent user interviews, pilots, revenue, wallet counts or customer demand. Add real validation only if actually collected before submission. + +## Pre-existing-work disclosure + +CommitLedger existed before the Crypto World's Fair contest window. The submission must disclose that history. Do not present the entire codebase as newly created during the contest. Identify competition-window work from Git history and describe only what was actually completed between the official start and end dates. + +## Presentation video + +Use `docs/COLOSSEUM_PITCH_SCRIPT.md`. + +## Product demo + +Use `docs/COLOSSEUM_DEMO_SCRIPT.md`. + +## Repository / links + +Repository: final judge-accessible CommitLedger URL. + +Live proof: +https://commitledger-proof-final.onrender.com + +Proof JSON: +https://commitledger-proof-final.onrender.com/api/proof + +## Final receipt fields + +Populate after actual portal actions: +- HackCanton submission URL/receipt: +- Colosseum project URL: +- Colosseum submission confirmation: +- Presentation video: +- Demo video: From 96eee9f557dcc2ad3ee0c22a91048901480add4f Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 7 Oct 2026 10:51:10 +0600 Subject: [PATCH 141/144] docs: add docs/RULES_RECHECK_20261007.md for Oct 7 submission sprint --- docs/RULES_RECHECK_20261007.md | 72 ++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 docs/RULES_RECHECK_20261007.md diff --git a/docs/RULES_RECHECK_20261007.md b/docs/RULES_RECHECK_20261007.md new file mode 100644 index 0000000..de6e75d --- /dev/null +++ b/docs/RULES_RECHECK_20261007.md @@ -0,0 +1,72 @@ +# Rules Recheck — 7 October 2026 + +This document records the current public rule/deadline facts used for the active CommitLedger submission sprint. Logged-in/private portal requirements still override this file. + +## HackCanton Season 3 + +Current public schedule: +- Opening: 17 Sep 2026. +- Build: 18 Sep–9 Oct 2026. +- Submission deadline: **9 Oct 2026, 23:59 UTC**. +- Bangladesh time: **10 Oct 2026, 05:59**. +- Closing: 10 Oct. +- Judging: 10–18 Oct. +- Finalists: 19 Oct. +- Grand Final: 21 Oct, 14:00 UTC. + +Public materials also describe: +- fully online; +- solo or team participation; +- five tracks; +- up to $50K cash + credits; +- Canton-native product focus. + +Public recheck sources: +- https://appsfactory.cc/hackathons +- https://forum.canton.network/ +- https://cantonnews.org/hackcanton-season-3-reaches-85-teams-as-submissions-begin +- https://cantonnews.org/events + +The public AppsFactory page currently exposes only a shell to unauthenticated retrieval, so private/formal portal fields are **not verified here**. + +A current ecosystem report notes that projects need the actual submission step in addition to uploading materials and mentions demo, code repository and video among the project materials. + +### Portal-only warning + +The repository contains historical external readiness gates named `mana1000`, `activity10Days`, `journalNonEmpty`, `projectProfileComplete`, and `track1Selected`. Treat these as historical portal assumptions until the logged-in current portal confirms whether they are mandatory. + +## Crypto World's Fair — Colosseum + +Official competition page: +https://colosseum.com/worldsfair + +Official rules PDF: +https://colosseum.com/legal/Crypto%20World%27s%20Fair%20Hackathon%20Rules.pdf + +Official hackathon FAQ: +https://colosseum.com/hackathon?year=fall2026 + +Verified from official rules: +- No purchase necessary. +- Contest starts **14 Sep 2026, 6:00 AM PT**. +- Contest ends **12 Oct 2026, 11:59 PM PT**. +- Bangladesh equivalent: **13 Oct 2026, 12:59**. +- Each member must register before the cutoff. +- Entrant must be age of majority in country/residence or at least 18, whichever is older. +- Bangladesh is not listed among the current excluded jurisdictions. +- One entrant may be a member of one team. +- A team may submit one project at a time. +- All submitted content must be in English. +- Judging criteria include functionality, potential impact, novelty, UX, open-source/composability and business plan. + +Verified from the current official FAQ: +- pre-existing code may be used; +- relevant prior development must be disclosed; +- judging focuses on work completed during the hackathon window; +- repository link is requested; +- private repository is allowed if review access is granted to `hackathon@colosseum.com`; +- presentation video is requested; +- <=3 minute product-demo video is requested; +- GTM, demand validation and distribution plans are requested. + +If the live portal or updated official rules differ at submission time, the live official source wins. From 601bc9b760586e0c6c6f426b6f9054d7e6f50857 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 7 Oct 2026 10:51:12 +0600 Subject: [PATCH 142/144] docs: add docs/SUBMISSION_STATUS_20261007.md for Oct 7 submission sprint --- docs/SUBMISSION_STATUS_20261007.md | 77 ++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 docs/SUBMISSION_STATUS_20261007.md diff --git a/docs/SUBMISSION_STATUS_20261007.md b/docs/SUBMISSION_STATUS_20261007.md new file mode 100644 index 0000000..c5df840 --- /dev/null +++ b/docs/SUBMISSION_STATUS_20261007.md @@ -0,0 +1,77 @@ +# Submission Status — 7 October 2026 + +## Executive summary + +CommitLedger's **technical product is finished and proven**. The remaining work is primarily portal eligibility/registration, videos, judge access and final submission actions. + +### Planning readiness estimate + +These percentages are planning estimates, not organizer scores. + +| Area | HackCanton | Colosseum | +|---|---:|---:| +| Core product | 100% | 100% | +| Executed technical proof | 100% | 100% | +| Written submission material | 90% | 80% | +| Rules/deadline research | 85% | 95% | +| Recorded media | 0% | 0% | +| Portal registration/profile | Unknown until login | Unknown until login | +| Final submission/receipt | 0% | 0% | +| **Artifact-work estimate** | **~70%** | **~65%** | + +The uncertainty is external: a portal-only eligibility gate can override otherwise high artifact readiness. + +## What is already done + +- Canton/Daml workflow implemented. +- Role separation implemented. +- Canonical GitHub evidence verifier implemented. +- Full six-step lifecycle proven. +- SettlementReceipt proven. +- Three negative/security rejections proven. +- Node 86/86 PASS. +- Daml build/tests PASS. +- Frozen proof commit documented. +- Public proof service documented. +- HackCanton pitch ready. +- HackCanton demo script ready. +- Colosseum submission copy ready. +- Colosseum pitch script ready. +- Colosseum demo script ready. +- AI disclosure ready. +- Hackathon/pre-existing-work disclosure framework ready. +- Rules/deadlines rechecked on 7 Oct. + +## What remains for HackCanton + +1. Log into AppsFactory. +2. Confirm registration/eligibility/project state. +3. Confirm any Mana/activity/journal/profile gates. +4. Select/confirm track. +5. Record final <=5-minute video. +6. Make repository/proof links judge-accessible. +7. Fill final portal fields. +8. Submit. +9. Save receipt. + +## What remains for Colosseum + +1. Log in/register and join the competition. +2. Create/confirm project. +3. Add logo/graphic. +4. Add only real demand validation. +5. Record 2–3 minute presentation video. +6. Record <=3 minute demo. +7. Confirm repository review access. +8. Complete English portal fields. +9. Submit. +10. Save receipt. + +## Hard deadlines + +- HackCanton: **9 Oct 2026 23:59 UTC / 10 Oct 05:59 Bangladesh time**. +- Colosseum: **12 Oct 2026 23:59 PT / 13 Oct 12:59 Bangladesh time**. + +## Do not change + +Do not rewrite core product code merely for a new submission. If executable code changes, rerun the full technical proof. From 359483e5130aeccf8431bd6b50b59be76bc47671 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 7 Oct 2026 10:51:14 +0600 Subject: [PATCH 143/144] docs: add docs/COLOSSEUM_PITCH_SCRIPT.md for Oct 7 submission sprint --- docs/COLOSSEUM_PITCH_SCRIPT.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 docs/COLOSSEUM_PITCH_SCRIPT.md diff --git a/docs/COLOSSEUM_PITCH_SCRIPT.md b/docs/COLOSSEUM_PITCH_SCRIPT.md new file mode 100644 index 0000000..dc9ae52 --- /dev/null +++ b/docs/COLOSSEUM_PITCH_SCRIPT.md @@ -0,0 +1,34 @@ +# Colosseum Presentation Script — CommitLedger + +Target: **about 2 minutes**, comfortably inside the current 2–3 minute FAQ guidance. + +## 0:00–0:15 — Hook + +“Open-source programs can prove that a pull request merged. What they often cannot prove is who was authorized to verify the work, who was authorized to settle it, and whether the same contribution was paid twice. CommitLedger is the control layer between merged work and authorized settlement.” + +## 0:15–0:40 — Product + +“CommitLedger binds a canonical GitHub issue and merged pull request to a role-authorized workflow: Bounty, Claim, Pull Request, Verification, Settlement, and a final SettlementReceipt. The maintainer, contributor, and verifier have different permissions, and the workflow only advances when the evidence and authorization are correct.” + +## 0:40–1:05 — Why blockchain / why Canton + +“GitHub is the source of truth for the work event. Canton and Daml are the source of truth for settlement workflow state. Daml defines the allowed transitions, consumed contracts block replay, and the ledger gives us inspectable contract and update identifiers. If you remove the ledger layer, the core authorization and replay protection disappear.” + +## 1:05–1:30 — Proof + +“We executed the full workflow on a fresh Canton sandbox. The Node suite passed 86 out of 86 tests, Daml built and tested successfully, all six ledger transitions completed, and the final SettlementReceipt was generated. We also captured three negative proofs: wrong evidence, unauthorized settlement, and duplicate settlement.” + +## 1:30–1:50 — Market + +“Our first customers are organizations running contributor and bounty programs: open-source companies, foundations, ecosystem funds, and engineering organizations. The wedge is one repository and one bounty campaign, then expand into organization policy, audit controls, and approved production settlement adapters.” + +## 1:50–2:00 — Close + +“CommitLedger separates evidence from authority: GitHub proves what happened; the verifier attests it; and Canton controls what each party is allowed to do next.” + +## Recording rules + +- English only. +- Do not claim revenue, customers, MainNet, Canton Coin or fiat settlement. +- Mention that pre-existing development is disclosed in the submission. +- Keep the product and founder reasoning clear; avoid a long technical walkthrough here. From d582f3a008a5d0eb658b1c4bb0a4dd5007de6705 Mon Sep 17 00:00:00 2001 From: Saidur-droid Date: Wed, 7 Oct 2026 10:51:17 +0600 Subject: [PATCH 144/144] docs: add docs/COLOSSEUM_DEMO_SCRIPT.md for Oct 7 submission sprint --- docs/COLOSSEUM_DEMO_SCRIPT.md | 68 +++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 docs/COLOSSEUM_DEMO_SCRIPT.md diff --git a/docs/COLOSSEUM_DEMO_SCRIPT.md b/docs/COLOSSEUM_DEMO_SCRIPT.md new file mode 100644 index 0000000..4feb696 --- /dev/null +++ b/docs/COLOSSEUM_DEMO_SCRIPT.md @@ -0,0 +1,68 @@ +# Colosseum Product Demo Script — CommitLedger + +Target: **2:15–2:45**, under the current <=3 minute FAQ limit. + +## 0:00–0:15 — Start on proof summary + +Show: +- product name; +- frozen proof commit; +- runtime verified state. + +Say: +“CommitLedger turns canonical GitHub contribution evidence into a role-authorized Canton settlement receipt.” + +## 0:15–0:40 — External work evidence + +Show the real evidence fixture: +- repository `Saidur-droid/MergeEarn`; +- open issue `#69`; +- merged PR `#73`. + +Say: +“This is a real GitHub evidence fixture, not a traction claim. CommitLedger verifies the canonical issue and merged pull request before settlement can advance.” + +## 0:40–1:35 — Six-step lifecycle + +Show the workflow: +1. Bounty created. +2. Claim created. +3. Claim accepted. +4. Pull request submitted. +5. Merge evidence verified. +6. Work settled. + +Expand at least one real `updateId` and `contractId`. + +End on the `SettlementReceipt`. + +## 1:35–2:15 — Security proof + +Show the three rejection cases: +- wrong issue/evidence; +- unauthorized settlement attempt; +- duplicate/replay settlement. + +Say: +“These are application-level authorization and evidence failures captured from the proven run, not network errors presented as security proof.” + +## 2:15–2:35 — Architecture + +Show the boundary: +- GitHub = external work evidence; +- verifier = attestation boundary; +- Daml = allowed state transitions; +- Canton = workflow state and receipt. + +## 2:35–2:45 — Close + +“CommitLedger makes settlement decisions inspectable, role-aware and replay-safe.” + +## Before upload + +- keep <=3:00; +- English only; +- hide tokens/secrets; +- ensure every displayed proof element belongs to the frozen proof commit; +- do not imply DEMO_CREDIT is money; +- test video and proof links as a logged-out judge.