Skip to content

feat(arc-adapter): Arc profiles, money, config, and readiness probe (B01 core) - #11

Merged
selezenart merged 5 commits into
developfrom
milestone/b01-sdk-network-compatibility
Sep 7, 2026
Merged

selezenart merged 5 commits into
developfrom
milestone/b01-sdk-network-compatibility

Conversation

@selezenart

@selezenart selezenart commented Sep 7, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Implements the core of B01 — SDK and Arc network compatibility for the Coder B
lane as a standalone package, packages/arc-adapter.

It pins the toolchain, encodes the Arc deployment profiles, defines the
settlement-config-v1 variable surface, and adds a fail-closed readiness probe
and a sanitized redaction boundary. The package installs, lints, typechecks,
tests, and builds on its own, with no root workspace composition and no
credential, so the packet closes independently as the lane's independence model
requires.

This is a partial B01. See "Anything a reviewer should know" below.

Scope and acceptance criteria

  • The change is limited to the stated milestone or issue.
  • Acceptance criteria are listed and satisfied.
  • No unrelated cleanup is included.

Acceptance criteria addressed, per milestones/coder-b/B01-sdk-network-compatibility.md:

  • B01.1 version compatibility matrix — Node, TypeScript, viem, Vitest, and
    ESLint tested together in an isolated package. Rejected combination recorded
    below.
  • B01.2 Arc deployment profiles — testnet constants pinned; mainnet profile
    present but valueless; settlement amounts separated from display formatting;
    no silent network, token, or precision override.
  • B01.4 configuration schema — every variable classified public / secret /
    optional / human-only, with placeholder-only example rendering.
  • B01.5 readiness probe library — chain ID and token bytecode assertions,
    identity-format validation that prints no credential, and a hard split
    between "unavailable" and "identity mismatch".
  • B01.6 fixture capture boundary — deny-by-default redaction plus an
    assertion helper usable as a test guard on committed fixtures.

Not in this PR: B01.3 (Memo and policy compatibility spike) and the
testkit-settlement fixture package.

Product and security invariants

  • Tenant isolation remains fail-closed.
  • Sponsor authorization, auditability, and daily caps remain enforced where applicable.
  • Recipients cannot modify sponsor controls or access sponsor-only data.
  • No secret, token, production identifier, or personal data is committed or pasted into review prompts.
  • Any non-applicable invariant is explained below.

Invariant notes:

No durable state, no domain table, no migration, and no transaction submission
is touched, so the tenant-isolation and sponsor-control rows are not applicable
to this diff. The invariants this change does carry:

  • Money is never floating point. Values are canonical integer strings and
    bigint. There is no number arithmetic on a monetary value and no parser
    from number. Comparison against the spending cap is bigint, so an amount
    above the cap cannot slip through by float rounding.
  • Amounts are rejected, not normalized. "01", "1.0", "+1", "1e6"
    and " 1" are errors. Accepting them would let two distinct strings describe
    one amount and break the payload fingerprint that the duplicate-settlement
    guard depends on.
  • Testnet only. Arc Testnet is the sole enabled profile. Enabling a mainnet
    profile requires pinned values, an enabled flag, and explicit human
    authorization together; authorization alone is refused, and the mainnet
    profile has no values to authorize.
  • Secrets. ONESHOT_PRIVY_APP_SECRET is classified secret and is read by
    nothing in this package. Redaction is deny-by-default on both key name and
    value shape, so an unclassified new provider field is redacted rather than
    leaked.

Validation

Commands and results:

cd packages/arc-adapter
npm install    : PASS (156 packages, 0 vulnerabilities)
npm run lint      : PASS (eslint, strictTypeChecked)
npm run typecheck : PASS (tsc --noEmit)
npm run test      : PASS (5 files, 99 tests)
npm run build     : PASS (tsc --project tsconfig.build.json)
git diff --cached --check : PASS (no whitespace errors)
staged-tree secret scan   : PASS (only an npm integrity hash and the
                            deliberate fake key literal the redaction
                            detector is tested against)

Independent review evidence

Gate A — exact candidate tree before push

  • Base commit SHA: 9dc541d08daf4e9a9c338c562fb1fbe6ac6be04a (branch point)

  • Candidate tree SHA: 5e49be7a785149da36b40861ad55a6ad6e8e2a26

  • Candidate commit SHA: a1d8599afef17e668afe26fc26cccca1b7db4103

  • Reviewer tool: free-pi-cli

  • Reviewer model: glm-5.3-flash

  • Verdict: PASS, but bound to a superset tree, not this exact head.

  • Findings or residual risks: a FreePi review returned VERDICT: PASS with zero
    blocking findings over tree 3351a19e455024cf65de3f9fb400d8eac5df4080, which
    is the B02 branch containing B01 and B02 together. Every file in this PR was
    reviewed, but that tree is not this PR's head tree
    (b7b6942d46f0cc05d4c5784eee49ed68300777d0), so the exact-tree binding that
    .agent/IMPLEMENTATION_LOOP.md requires is not satisfied. Treat the content
    as reviewed and the tree identity as unbound.

  • The reviewed tree equals the committed tree.

Gate B — exact remote PR head

  • Reviewer tool: free-pi-cli

  • Verdict: NOT RUN

  • Gate B reviewed the current remote head and matches Gate A's approved tree.

  • Agent policy / repository-policy and all applicable CI checks pass (repository-policy, Markdown and Mermaid, ESLint and TypeScript all pass; Workers Builds: oneshot fails on develop too and is pre-existing).

Both gates are unrun on the owner's instruction to proceed without them while
no deployment target exists. Treat this PR as not independently reviewed.
Package-local validation above is the substitute evidence.

Risk and rollback

Residual risks:

  • Arc constants are not yet verified against official Arc documentation.
    Chain 5042002 and the USDC interface 0x3600...0000 are taken from
    milestones/CONTRACTS.md, not from a primary source. B01.2 requires the
    primary-source check before these are treated as pinned. The readiness probe
    is what catches a wrong value at runtime, but the constants themselves still
    need confirming.
  • Dependency versions are pinned exact and will need a scheduled refresh;
    eslint@9.39.1 already reports as outside its supported window.
  • IDENTIFIER_SHAPE for Privy wallet and policy IDs is a conservative shape
    guess. B02 should replace it with the real documented format.

Rollback: additive new package on a short-lived branch. Revert the commit or
close the PR. No migration, deployment, shared configuration, or durable state
is involved.

Human merge

  • A human owner has reviewed the evidence and will perform the merge.

Anything else a reviewer should know

Base is develop, not main. .agent/AGENTS.md and
.agent/IMPLEMENTATION_LOOP.md forbid targeting main for feature work.

Two judgment calls worth checking:

  1. No endpoints in the profile table. The first draft of profiles.ts
    contained invented rpcUrl and explorerUrl hostnames. That is exactly the
    guessed default that develop@d6758dd removed from the plan, so they were
    deleted. RPC and explorer are now operator configuration, and
    profiles.test.ts asserts that no profile contains any http(s):// string
    so they cannot creep back.

  2. TypeScript 7 rejected. TypeScript 7.0.2 is published, but
    typescript-eslint constrains typescript to >=4.8.4 <6.1.0 at every
    published version including the latest 8.69.0. Choosing TS 7 would mean
    dropping type-aware linting on the package that validates chain identity and
    money. Pinned TypeScript 5.9.3 instead.

One bug the tests caught: the redaction key matcher originally missed
x-api-key, because the pattern list held apikey and api_key but not the
hyphenated spelling. Key matching now strips case and separators before
comparing, so one pattern covers every spelling a provider might use.

Implements the B01 core for Coder B as a standalone package with its own
install, lint, typecheck, test, and build commands, so the packet closes
without root workspace composition or any credential.

Arc Testnet is the only enabled deployment profile and pins the constants
frozen in milestones/CONTRACTS.md: chain 5042002, eip155:5042002, the USDC
interface at 0x3600...0000, and six-decimal precision. The Mainnet profile is
structurally present but carries no chain ID, RPC, explorer, or token value at
all. develop@d6758dd removed a previously asserted mainnet chain and launch
date as unverified guesses, and a test now asserts no profile contains any
endpoint so they cannot creep back in. RPC and explorer URLs are operator
configuration rather than profile constants for the same reason.

Money is integer atomic units and bigint only. parseAmountAtomic rejects
rather than normalizes non-canonical input, because accepting both "1" and
"01" would let two strings describe one amount and break the payload
fingerprint the duplicate-settlement guard depends on. Display formatting is
string slicing, never division.

The readiness probe separates UNAVAILABLE from MISMATCH. A wrong chain ID or a
token address holding no bytecode is a permanent, human-fix condition and must
never be retried into working; an unreachable endpoint may resolve on its own.
Both block readiness, and the probe is driven through a small RPC interface so
it runs fully offline.

Configuration classifies every variable as public, secret, optional, or
human-only, and fails closed on a missing, malformed, or contradictory value.
Enabling a mainnet profile requires pinned values, an enabled flag, and
explicit human authorization together; authorization alone is refused.

Redaction is deny-by-default on key name and on secret-shaped content, so an
unclassified new provider field is redacted rather than leaked. Key matching
strips case and separators after a test caught x-api-key slipping past an
apikey pattern.

Toolchain pinned by the B01.1 compatibility spike: Node >=22.12, TypeScript
5.9.3, viem 2.56.3, Vitest 5.0.0, ESLint 9.39.1. TypeScript 7.0.2 is rejected
because typescript-eslint constrains typescript to <6.1.0 at every published
version.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 7, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
oneshot 798f00e Sep 07 2026, 01:35 PM

Completes B01.2 verification and the B01.3 spike against primary sources, and
adds the Privy policy scope model with a deny fixture per constrained
dimension. Source URLs and evidence are recorded in
.agent/research/20260907-b01-arc-privy-verification.md.

Arc verification. Chain 5042002 and the USDC interface at 0x3600...0000 are
confirmed correct against docs.arc.io, so no frozen value changed. The check
did surface one gap: Arc's native gas asset and its USDC ERC-20 interface are
both named USDC but use different precision, 18 decimals for gas and 6 for the
ERC-20 interface, a factor of 10^12 apart. Profiles now carry nativeDecimals
separately from tokenDecimals, and the readiness probe reports MISMATCH if a
profile equates them or declares native decimals as anything but 18. This is
the separation B01.2 asks for between settlement amounts and gas accounting.

The verification also confirmed that the RPC hostname guessed in the first
draft was wrong as well as against policy: Arc publishes four testnet
endpoints under arc.io, not one under arc.network. Endpoints stay operator
configuration.

Memo policy spike, B01.3. Recorded NOT_SUPPORTED. Privy policy conditions
decode the arguments of the function the wallet actually calls. On the Arc Memo
path that is the Memo function, so the forwarded transfer's recipient and
amount sit in an inner call no documented condition reaches. B01.3 permits
SUPPORTED only with deny fixtures for every wrong dimension, and those two have
none available, so claiming support would be false. v1 settles with a direct
USDC ERC-20 transfer, which is fully constrainable, and memo_id stays unused.

evaluateScope mirrors the remote policy locally with an independent deny reason
per dimension: wrong chain, wrong destination contract, non-zero native value,
wrong method, wrong recipient, wrong amount, and malformed calldata. The
duplication is deliberate. A Privy policy lives in provider configuration and
can drift, and the local check can only refuse, never grant. Calldata length is
checked exactly so appended bytes cannot ride through a selector prefix match.

Also publishes the settlement-config-v1 handoff artifact and generates
.env.example from the config schema so the two cannot drift.
…narios

Adds the B01 readiness simulator so the probe can be exercised across every
outcome with no network and no credential, which is what lets the packet close
independently of live Arc access.

The simulator covers the healthy path, the two permanent misconfigurations
(wrong chain, and a token address holding no code, including a null code
answer), and the transient faults (unreachable endpoint, partial availability
where the chain reads but the token read fails, and an oversized provider
error body).

Tests assert the distinction the probe exists to make: wrong chain and missing
bytecode set hasMismatch, while unreachable endpoints do not. Misclassifying a
transient fault as a mismatch would send an operator hunting a configuration
bug that does not exist; misclassifying a mismatch as transient would invite a
retry loop against the wrong chain.

An unknown scenario name throws rather than returning a healthy probe, per the
repository rule that simulators never silently default an unknown enum to a
successful or retryable result.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant