SDK #20 (draft): dincli/sdk foundation — loaders + manifest/runtime + state/serialize - #31
Conversation
Documentation/ now describes only what exists in code on develop (not the live Sepolia deployment), split by audience: - public/ — participant-facing: setup, cli-reference (was common.md), getting-started, manifest, services, roles/, workflows/, guides/ - technical/ — internal: contracts/ (per-contract refs), mechanisms/, services/, testing/, upgradable-contracts/, ARCHITECTURE.md (placeholder moved from Developer/, lands via P3-DOC1) Developer/ holds forward-looking material: - design/ (new) — MECHANISM_DESIGN.md, suggested-staking-mechanism.md (moved from Documentation/technical/mechanisms — it specifies planned behavior, not current), feasibility-report.md - proposals/ (was tooling/) — proposals for tools not yet built - issues/, tasks/, discussion/, rejected-ideas/ unchanged Also: - Rewrote both index READMEs with the placement rule and layout - Added "where does a new doc go" + graduation rule (shipped designs get rewritten as current-state in Documentation/technical/, not moved) - Fixed all repo-internal links to moved files (CLAUDE.md, README.md, Developer/*, dincli/docker/node/README.md) and repaired relative links inside moved files; fixed dead blob/main GitHub URLs to blob/develop - Added DevNet 2.0 mechanism design doc (design/MECHANISM_DESIGN.md) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nnect-wallet fixes + OWS spike Squash of three commits from the PR InfiniteZeroFoundation#16 review branch (634ce1e, 5cd1a9b, 6905808): - list-accounts source tag: [..] was parsed by Rich as a markup tag and dropped; use (..) so imported/created/legacy renders. Docker node README: .session is a file, not a directory (review bugs InfiniteZeroFoundation#1, InfiniteZeroFoundation#6). - connect-wallet: confirm before overwriting an existing named keystore (--yes/-y to bypass, mirrors send-eth), never encrypt a new keystore with a stale in-memory cached password, and parse .env once per unlock instead of per _get_password call (review items InfiniteZeroFoundation#2, InfiniteZeroFoundation#3, InfiniteZeroFoundation#5). Adds 7 tests. - OWS delegation feasibility spike (Developer/discussion/ ows-delegation-feasibility.md): hands-on verification that OWS signs an EVM tx without exposing the key; scoped-policy delegation remains unproven/follow-up. Corrects wallet-setup.md's broken OWS export-keystore flow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cherry-picked from PR InfiniteZeroFoundation#28, relocated from Documentation/public/guides/ to Developer/proposals/: this documents research into a storage provider dincli does not yet integrate (no FOC/filecoin-pin adapter exists in ipfs.py), so per Developer/README.md's placement rule it belongs with forward-looking tooling material, not with docs of what already exists on develop. Replaces the Storacha guide (PR InfiniteZeroFoundation#26, closed) as the storage recommendation for Discussion InfiniteZeroFoundation#18: Storacha's infrastructure (console, API host, and both storacha.link/w3s.link retrieval gateways) is down as of this research -- Cloudflare Error 1000 on the apex domain, NXDOMAIN on console/API subdomains, gateways redirecting into the broken root. Not a frontend-only issue. Covers Filecoin Pin (the filecoin-pin CLI/library, built on FOC's Synapse SDK) in the same three-part structure as the Storacha guide: Web setup, CLI upload/retrieve, and authentication -- wallet keys and the Session Key delegation model, which turns out to be a much closer parallel to Storacha's UCAN delegation than raw-key-or-nothing. Fact-checked against the filecoin-pin GitHub repo directly (README, CLI source in src/commands/, documentation/retrieval.md and content-routing-faq.md) rather than docs.filecoin.io, which 404'd on the CLI walkthrough page during this research. Caught and corrected one safety-critical error from an earlier research pass: the CLI defaults to Mainnet (real funds), not Calibration testnet as initially assumed.
Discussion InfiniteZeroFoundation#18 hands-on testing ruled out both providers this doc recommended: Lighthouse retrieval is payment-gated (fails the "readers pay nothing" requirement), and Storacha's infrastructure is down/deprecated. Point at the new Filecoin Onchain Cloud proposal (Developer/proposals/filecoin-onchain-cloud.md) instead of leaving a stale recommendation for the next reader to walk into. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…n guide (relocated) Manually merged PR InfiniteZeroFoundation#28 by Similoluwa Abidoye. Content unchanged from the PR except relocation from Documentation/public/guides/ to Developer/proposals/: FOC/filecoin-pin is not integrated into dincli (no code changed here, ipfs.py still only supports env/filebase/custom), so per Developer/README.md's placement rule this is forward-looking research material, not documentation of what exists on develop. Also updates Developer/discussion/add-filecoin-support.md, which previously recommended Lighthouse and Storacha as the best Filecoin-backed providers -- both since ruled out by this same research (Discussion InfiniteZeroFoundation#18) -- so it no longer contradicts the new proposal doc. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
InfiniteZeroFoundation#20) v0.3 proposal for the dincli/sdk/ extraction: five design principles, the JSON envelope contract, DinError taxonomy with reserved tx/rpc subcodes, non-interactive DinSession/SignerProvider, plan/apply (three-phase for CID-producing ops) confirmation model, richer TxReceiptInfo, and backward- compat guarantees. Candidate seed for P3-DOC7. Incorporates two rounds of code-audit against real call sites. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…niteZeroFoundation#22) Manually merges the Part 1 deliverable of task_060726_4 from Similoluwa Abidoye (@Abidoyesimze, PR InfiniteZeroFoundation#22, branch security/foundry-src-review). The 7 contracts in foundry/src/ are byte-identical between the audit's pinned commit (d136ff3) and current develop, so the findings and line references apply as-is to the live contracts. - Documentation/technical/audits/foundry-src-security-review.md (relocated from the PR's Developer/audits/ path) - foundry/test/SecurityFindings.t.sol — 9 PoC/verification tests, all passing (forge test --match-contract SecurityFindingsTest). Findings: 0 Critical, 4 High, 4 Medium, 8 Low/Informational. Open follow-ups tracked on PR InfiniteZeroFoundation#22: severity of H-1/H-2 (candidate for Critical) and a measured spec-scale gas number for H-1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…h real forge measurements Follow-up to PR InfiniteZeroFoundation#22 review feedback: 1. Severity: H-1 (Sybil-cheap gas-DoS) and H-2 (zero-CID sentinel collision) elevated to Critical (now C-1/C-2). Both permanently brick a Global Iteration on non-upgradeable contracts with no recovery path, and both are cheap (C-1) or free (C-2) for a single unprivileged participant to trigger. Remaining High findings renumbered H-1/H-2; no finding content changed, only labels. 2. Gas number: added test_gas_finalizeEvaluation_and_slashAuditors_atScale, measuring real forge gas at two registrant scales (10 and 30 batches at demo params) and deriving the real marginal per-batch and per-(auditor,model)-pair cost from the slope between them. Params.auditorsPerBatch/modelsPerBatch has no setter, so literal spec-scale batch *internals* can't be deployed without modifying the contract (out of scope) -- the report is explicit that the final spec-scale projection layers a structural extrapolation on top of real measured data, not a second measurement, and that the slashAuditors number is a measured floor (its early-break behavior under an all-Sybil, zero-vote attack undermeasures the function's true worst case). Result: 223.5M gas (finalizeEvaluation) / 169.5M gas floor (slashAuditors) projected at full spec scale -- 5.6x-7.5x over Optimism's ~30M block limit, corroborating and quantifying the original estimate rather than changing its conclusion. 10/10 tests passing in SecurityFindingsTest. Reproduced the maintainer's UpgradeValidation.t.sol FFI/npx failure independently in a second environment -- confirmed not related to this report's PoCs.
…ary test (InfiniteZeroFoundation#20) First clean-seed extraction for the SDK layer: - dincli/sdk/cid.py: moved verbatim from services/cid_utils (zero cli deps); services/cid_utils.py kept as a back-compat re-export shim (13 importers). - dincli/sdk/errors.py: DinError hierarchy + reserved tx/rpc subcodes + allowlist-oriented sanitize_details (proposal §4/§4a). - tests/test_sdk_boundary.py: fresh-subprocess import-boundary test enforcing 'sdk imports no typer/rich/dincli.cli', plus error/sanitizer unit tests. runtime/ipfs/contracts deferred to the loaders step: they import cli.utils/log at module load, so moving them now would fail the boundary test until config/manifest/web3/log are extracted. Unit suite green (76 passed, 1 skipped). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…iteZeroFoundation#20) Move the dependency-root loader cluster out of dincli/cli into dincli/sdk, leaving the old paths as re-export shims: - sdk/config.py: config/env/network/IPFS-config resolution + constants + IPFSConfig; console.print -> logger; logging.getLogger("dincli") directly (no sdk.log import -> no config<->log cycle). - sdk/log.py: logger + quiet _read_log_level() bootstrap (no missing-config warning at import). - sdk/web3.py: get_w3 (ConnectionError kept; # TODO(sdk-keystones)). - cli/utils.py: re-exports placed above the wallet constants they feed; wallet/keystore/manifest/tx code stays. - cli/log.py: re-exports logger + stdlib logging (dincli.cli.context needs both). Tests: add test_sdk_config.py (17) + test_sdk_web3.py (2); migrate test_connect_wallet's config-file patch to sdk.config. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n-seed (InfiniteZeroFoundation#20) - sdk/ipfs.py: 3-provider upload/retrieve from services/ipfs; imports redirected to sdk.config/sdk.log/sdk.cid; progress prints -> logger; # TODO(sdk-keystones): IpfsError. - sdk/contracts.py: ABIs + get_contract_instance; lazy get_w3 from sdk.web3. - services/ipfs.py, cli/contract_utils.py: re-export shims. - tests/test_ipfs_config.py: migrate config-file + ipfs-global patches to sdk.config / sdk.ipfs (re-export does not redirect a moved fn's globals). The dincli/sdk package now holds a dependency-clean foundation (cid, errors, config, log, web3, ipfs, contracts); the import-boundary test enforces no typer/rich/dincli.cli imports across all of it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Target architecture for the full stack — DAO/platform/task contracts, indexer, SDK, dincli/dind, IPFS layer, din-node/worker — with layered Mermaid + ASCII diagrams, key flows, and P4 dependency order. Links the live diagram on mermaidviewer.com and indexes the doc in Developer/README. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…proxies (PR InfiniteZeroFoundation#13) Land the platform-upgradeable work from PR InfiniteZeroFoundation#13: - DinCoordinator, DinToken, DinValidatorStake, DINModelRegistry converted to initializer-based transparent-proxy implementations - V2 stub implementations under hardhat/contracts/upgrade/ with upgrade test suites under hardhat/test/ - deploy-platform.ts / upgrade-platform.ts scripts plus shared deploy/{constants,helpers,types}.ts and test/helpers/platform.ts - MockSlasher test mock, hardhat-upgrades tooling deps, updated DinCoordinator/DinToken ABIs - .gitignore: stop ignoring hardhat/scripts and hardhat/test so the deploy/upgrade tooling is tracked on develop Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…) with dincli harness, docs, wallet split Follow-up integration on top of the PR InfiniteZeroFoundation#13 contract work: - dincli test harness (tests/dincli/) updated to deploy the platform via the interim deploy-platform.ts script + import-deployments flow; hardhat/deployments/localhost.json and din_info.json tracked for it - dincli: register-wallet/connect-wallet split follow-ups in cli/{context,core,system,utils}.py and main.py; regenerated DINModelRegistry/DinValidatorStake ABIs - Documentation/technical/contracts/hardhat/: per-file docs for the new deploy/upgrade tooling, V2 contracts, and test suites (with coverage gaps); refreshed platform contract docs for the proxy conversion - Documentation/technical/testing/: new docs for dintoken, ipfs-config, cache-client-dp, connect-wallet test suites; testing-guide and containerization-guide updates - Documentation/public/: wallet-setup, keystore-migration, setup, getting-started, cli-reference, model-workflow updated for the wallet split and upgradeable deployment - Developer/issues/dincli-native-proxy-deployment.md: backlog Option C (native dincli proxy deployment); retire technical/ARCHITECTURE.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… the loader tier (InfiniteZeroFoundation#20) Move the manifest / din_info functions and ServiceRuntimeContext into dincli/sdk, leaving the old paths as backward-compat shims: - sdk/manifest.py: load_din_info, save_din_info, load_cid_services, download_manifest, get_model_info, get_manifest, get_manifest_path, get_manifest_key, is_ethereum_address; patchable DIN_INFO_PATH / CID_SERVICES_PATH constants; logging.getLogger("dincli") directly (no sdk.log import -> no cycle). Presentation stripped: cache_manifest's negative-id guard -> ValidationError; the info-block printing lifted out into a pure get_model_info returning data; the freshness warning -> logger.warning. - sdk/runtime.py: ServiceRuntimeContext + build_service_runtime_context, import repointed to sdk.manifest. - cli/utils.py: re-exports (above the wrapper); cache_manifest is now a thin CLI wrapper composing download_manifest + get_model_info + console.print and mapping ValidationError -> typer.Exit(1) (main.py has no DinError handler), reproducing the original info output verbatim. - services/runtime.py: re-export shim. Completes the SDK loader tier (cid, errors, config, log, web3, ipfs, contracts, manifest, runtime). No sdk<->cli cycle; import-boundary test now covers manifest+runtime. Tests: +22 (test_sdk_manifest), suite 117 passed / 1 skipped; behavior parity + tests/dincli import integrity verified. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…/home/azureuser constants.py now resolves everything from the repo checkout, home directory, or environment: repo root via __file__, venvs via ~/my_venvs/<name>, npx via the newest ~/.nvm/versions/node/*/bin/npx, scratch dir ~/tempdir/dincli. All resolutions are overridable via env vars (PYDIN_PYTHON, TORCHENV_PYTHON, TORCHENV_SITE_PACKAGES, NPX_BIN, IPFS_BIN, DIN_TEST_TMPDIR), read from the real environment first and then the repo-root .env. conftest.py and test_04_gi.py consume the shared constants instead of their own literals. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ences Rewrite all 101 remaining /home/azureuser occurrences across 17 files in Documentation/ and Developer/: - markdown links to repo files become relative links (GitHub-navigable) - shell/repo paths use the /path/to/devnet placeholder - venv and scratch paths use ~/my_venvs/... and ~/tempdir/dincli/ - HR profile pointers in migrate_to_foundy.md use ~/projects/HR/... - DIN_STATE_DIR example uses /home/<user>/.din-node; drop a stray duplicate mkdir line in containerization-guide.md Also repoint references to the six per-model service files (client/aggregator/auditor/model/scoring/modelowner.py) from dincli/services/ to cache_model_0/services/ — they moved there when services became per-model — and retarget the proposed contribution.py module to cache_model_0/services/ for consistency. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Working summary of Abraham Nash's DIN white paper (Jun 2026) with a gap/alignment checklist mapping paper mechanisms to DevNet status, feeding the P3 mechanism-design push. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…acking into docs - design/p3-design-plan.md: coordination page for the P3 mechanism-design push — mechanism -> design sources -> GitHub issue map (issues 36-43), open-decision -> discussion map (discussions 44-46), white-paper gap routing, definition of done - ROADMAP.md: P3 status note (delayed pending designs; P4 active in parallel; dates not binding) - MECHANISM_DESIGN.md, README.md: cross-references Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… (PR InfiniteZeroFoundation#35) Original work by Robert (@robertocarlous, PR InfiniteZeroFoundation#35, branch feature/foundry-platform-deployments, head 45dcda1), implementing issue InfiniteZeroFoundation#34: DeployPlatform.s.sol, UpgradePlatform.s.sol, four V2 upgrade stubs (DinTokenV2, DinCoordinatorV2, DinValidatorStakeV2, DINModelRegistryV2), the forge test suite (DeployPlatform.t.sol), dincli's `system import-deployments --foundry` flag, and the foundry.toml/package.json/.gitignore changes needed to run them. Three blocking bugs found in local verification, fixed here: - DeployPlatform.s.sol: vm.writeJson has no parent dir on a fresh clone (foundry/deployments/ is gitignored and not committed) — call vm.createDir before vm.writeJson. - All four V2 stubs: a plain `//` comment sat between the two `///` NatSpec lines, splitting the doc block so `@custom:oz-upgrades-from` never reached solc — UpgradePlatform.s.sol failed upgrades-core validation for every contract. Moved the `//` explanation above the NatSpec block so the `///` lines stay contiguous. - Both scripts' own documented "Usage (from repo root)" command didn't compile (foundry.toml/remappings.txt live in foundry/, not repo root) — changed the usage comments to `cd foundry && forge script ...`. Verified end-to-end: forge test (40/40 passing), and both scripts run against a local chain (deploy -> deployments JSON -> dincli system import-deployments --foundry -> upgrade, version() == 2 through the proxy for all four contracts). Co-Authored-By: umeradl <umermajeed.cto@gmail.com> Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… harness tests/dincli/ can now deploy the platform contracts via either the new forge script from PR InfiniteZeroFoundation#35 or the existing hardhat script, selected by PLATFORM_DEPLOY_TOOLCHAIN (tests/dincli/constants.py; default "foundry", matching `dincli system import-deployments`'s own default). Overridable via env var / .env for anyone who wants to keep exercising the hardhat path. - constants.py: FOUNDRY_DIR, FORGE_BIN, PLATFORM_DEPLOY_TOOLCHAIN, HARDHAT_DEV_ACCOUNT_0; DEPLOYMENTS_FILE now resolves under whichever toolchain's deployments/ dir. - test_01_platform.py: test_deploy_platform_via_script branches on the toolchain (forge script vs hardhat run); test_import_deployments_into_din_info keeps passing an explicit --file since import-deployments' own --foundry/--hardhat default-path resolution is relative to cwd, and the harness's cwd is the isolated DIN_TEMP scratch dir, not the repo root. - conftest.py: managed_services now starts the matching chain backend (Anvil via foundry/anvil.sh for "foundry", the existing Hardhat node for "hardhat" — both on chain-id 1337) and runs `forge build` when the foundry toolchain is active; `npx hardhat compile` still always runs since task-level contract deploys and dump-abi tests need hardhat's ABIs/bytecode regardless of which toolchain deployed the platform. Verified: full tests/dincli/test_01_platform.py (8/8) under both toolchains, and test_01 through test_03 (35/35) end-to-end under the new foundry default. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two follow-up fixes to files landed in b40fe2e (PR InfiniteZeroFoundation#35 foundry platform deploy/upgrade parity): - foundry/test/DeployPlatform.t.sol: the DinCoordinatorV2/ DinValidatorStakeV2/DINModelRegistryV2 imports sat mid-file, after the MockTaskContract definition — moved them up next to the DinTokenV2 import at the top, with the rest of the imports. - foundry/script/DeployPlatform.s.sol: added a `forge clean` step to the documented "Usage (from repo root)" comment, right after starting anvil. Without it, deploying then testing back-to-back after a recompile can hit upgrades-core's stale build-info bug ("Found multiple contracts with name ...") — reproduced again while re-verifying this change; `forge clean` resolves it. Also ran `forge fmt` over both files (line-wrapping only, no logic changes). Re-verified: forge clean && forge test — 40/40 passing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds staking-design.md and tokenomics-design.md, and updates the mechanism design, staking mechanism, and P3 plan docs to reflect them. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ions Records outcomes from Abraham's Slack sync (2026-07-21) against the open decisions tracked in MECHANISM_DESIGN §9, staking-design.md §5, and tokenomics-design.md §6: slashed-stake destination (50/50 burn/treasury), depositAndMint cap/retirement, emission decay with MAX_SUPPLY left open, dual-source burn policy, delegation/tombstoning scope, and the white-paper InfiniteZeroFoundation#46 alignment items. Fee denomination is resolved as a DIN/ETH split (protocol fees in DIN, validator network fees in ETH), not the DIN-only recommendation these docs previously carried. GitHub Discussions InfiniteZeroFoundation#44/InfiniteZeroFoundation#45/InfiniteZeroFoundation#46 themselves are not yet updated to reflect this — noted in p3-design-plan.md so the "resolved" checkbox isn't marked done prematurely. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…iteZeroFoundation#37/InfiniteZeroFoundation#42/InfiniteZeroFoundation#43) Scopes a 5-day platform-contract task combining the now-resolved staking, tokenomics, and fees/treasury designs: new DinTreasury and DinFeeRouter contracts, DinToken.burn(), DinCoordinator mint-cap/faucet-retirement/ treasury-routing, DinValidatorStake governable params + real jailing, and a DIN-denominated fee path alongside DINModelRegistry's existing ETH fees. Includes the exact deployment/wiring order and test plan. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nfiniteZeroFoundation#39/InfiniteZeroFoundation#40/InfiniteZeroFoundation#41/InfiniteZeroFoundation#38) Adds task_210726_6 (scoring validation, commit-reveal auditing, encrypted test-data distribution, per-GI reward engine bootstrap) plus a prior-work reference doc mapping BlockFlow/2CP/Shapley-value-data-valuation/IPFS onto the task's scoring and auditing mechanisms. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…capabilities task (issues InfiniteZeroFoundation#20/InfiniteZeroFoundation#21) Co-Authored-By: umeradl <umermajeed.cto@gmail.com>
OZ v5 TransparentUpgradeableProxy deploys one ProxyAdmin per proxy. The previous script read the admin from the token proxy only and wrote a single proxyAdmin key, leaving the other three addresses unrecorded. Now reads getAdminAddress for each of the four proxies and writes proxyAdminToken, proxyAdminCoordinator, proxyAdminStake, and proxyAdminRegistry into foundry/deployments/<network>.json.
Same fix as the Foundry script: read the ProxyAdmin address for each proxy individually and write proxyAdminToken, proxyAdminCoordinator, proxyAdminStake, and proxyAdminRegistry into the deployments JSON. PlatformAddresses type updated to match.
Update _DEPLOYMENTS_TO_DIN_INFO to map the four proxyAdmin* keys (proxyAdminToken, proxyAdminCoordinator, proxyAdminStake, proxyAdminRegistry) to their corresponding proxy_admin_* keys in din_info.json, replacing the single proxyAdmin → proxy_admin mapping.
…proxy-admin-addresses Fix/proxy admin addresses
…severity-and-gas-followups audit(foundry): elevate H-1/H-2 to Critical, add real gas measurements
# Conflicts: # tests/test_connect_wallet.py
… validate predicates (InfiniteZeroFoundation#20) Move GIstateToDes/GIstateToStr/GIstatestrToIndex and their backing data (states, stateDescription, GIstate_to_index) verbatim from cli/utils.py into a pure dincli/sdk/state.py; utils.py now re-exports them as a shim (same pattern as the ipfs/runtime waves) so existing call sites keep working unchanged. Add a GIState IntEnum (0-based, matching the Solidity enum ordinals) and two validate_* predicates lifted from DinContext's GI-state guards (validate_gi_state_equals / validate_gi_state_at_least), raising the SDK's ValidationError instead of printing + typer.Exit. The two context.py guards now delegate the decision to these predicates while keeping identical console output and exit codes (no CLI behavior change). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eroFoundation#20) Implement to_envelope() + a metadata-driven JSON encoder per proposal §3: the {status, data, error, meta} envelope with schema_version/sdk_version, and the encoder rules — uint256_string -> decimal string, omit, small-int passthrough, bytes/HexBytes -> 0x hex, address -> checksummed, enum -> name, Decimal -> string, and None -> null (except tagged omit). Extend the import-boundary test to assert state and serialize import clean of typer/rich/dincli.cli. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code review — dincli/sdk foundation (loaders + manifest/runtime + state/serialize)
Reviewed at commit 6eac109 (feat/din-sdk, contributor Santiagocetran's fork), worktree /home/umerm/tempdir/DIN/DraftPRs/pr-31, diffed against origin/develop at 45cbfad. Scope: issue #20. Everything below was reproduced directly — actual diffs read line-by-line, actual test runs, actual git-blame comparisons against the pre-move code — not taken from the PR body.
git diff origin/develop...HEAD --stat: 28 files changed, 2682 insertions(+), 1046 deletions(-).
Files changed — PR #31 unique, at HEAD commit 6eac109 (name-status)
A dincli/sdk/__init__.py
A dincli/sdk/cid.py
A dincli/sdk/ipfs.py
A Developer/design/sdk-interface-proposal.md
M dincli/cli/context.py
M dincli/cli/contract_utils.py
A dincli/sdk/contracts.py
M dincli/cli/log.py
A dincli/sdk/log.py
M dincli/cli/utils.py
M dincli/services/cid_utils.py
M dincli/services/ipfs.py
A dincli/sdk/config.py
A dincli/sdk/errors.py
A dincli/sdk/manifest.py
A dincli/sdk/runtime.py
A dincli/sdk/serialize.py
A dincli/sdk/state.py
A dincli/sdk/web3.py
M dincli/services/runtime.py
M tests/test_connect_wallet.py
M tests/test_ipfs_config.py
A tests/test_sdk_boundary.py
A tests/test_sdk_config.py
A tests/test_sdk_manifest.py
A tests/test_sdk_serialize.py
A tests/test_sdk_state.py
A tests/test_sdk_web3.py
What's good
state.pyGI-state converters are byte-identical to the pre-move code. Diffeddincli/sdk/state.py'sstateDescription/states/GIstateToDes/GIstateToStr/GIstatestrToIndexagainstgit show origin/develop:dincli/cli/utils.py(lines 565-642) — only whitespace differs. Genuinely verbatim, as claimed.- The new
GIStateIntEnum's ordinals match Solidity exactly. Checkedfoundry/src/DINShared.sol'senum GIstates { AwaitingDINTaskAuditorToBeSet, // 0 ... GIended // 22 }(23 members, 0-22) against thestateslist instate.py— same 23 names, same order, index-for-index.hardhat/contracts/DINShared.solmatches too (both toolchains use the identical enum). - The
context.pydelegation preserves exit-code/message semantics.validate_GIstate_ET_given_GIstateandvalidate_GIstate_LTE_given_GIstatenow callvalidate_gi_state_equals/validate_gi_state_at_leastand catchValidationError, but theconsole.print(f"[bold red]✗ {msg}. Current state: ...[/bold red]")+raise typer.Exit(1)fallback is untouched and fires on the same conditions as the pre-extraction inline checks. Traced both sides; logic is equivalent. validation_failed's detail allowlist matches whatstate.pyactually raises._ALLOWLIST["validation_failed"] = {"field": "str", "expected": "str", "actual": "str"}inerrors.pylines up exactly with thedetails={"field": ..., "expected": ..., "actual": ...}dict bothvalidate_gi_state_equalsandvalidate_gi_state_at_leastconstruct — nothing gets silently dropped bysanitize_details.serialize.py's encoder rules all work correctly on the edge cases I tried by hand (not just the happy-path tests already in the file): nested dataclasses, a list of dataclasses,uint256at2**256-1(Python's arbitrary-precisionintmeans no truncation regardless of value size), zero, and tagged-Nonefields all serialize correctly. See finding No. 3 for the one edge case that isn't clean.- The import-boundary test is real and actually covers the new modules, not just the old ones.
tests/test_sdk_boundary.py::test_sdk_imports_no_cli_or_uiruns in a fresh subprocess, walks every submodule underdincli.sdkviapkgutil.walk_packages, and explicitly asserts{"dincli.sdk.state", "dincli.sdk.serialize"} <= loadedbefore checking neithertyper/richnor anydincli.cli.*module got pulled in. This is exactly the test Umer asked for, and it's not vacuous — I ran it standalone and it passes. - Full test suite passes: 144 passed (torch-gated
tests/test_cache_client_dp.pyand the live-chaintests/dincli/directory excluded per the task brief — both need dependencies not present in this venv). This matches the PR body's claimed count. The SDK-focused subset (test_sdk_state.py,test_sdk_serialize.py,test_sdk_boundary.py,test_sdk_manifest.py,test_sdk_config.py,test_sdk_web3.py) is 70 of those 144 and all pass on their own. - Two of the three loader-tier shims I spot-checked in depth (
dincli/services/runtime.py,dincli/services/cid_utils.py) are genuinely behavior-identical — diffed them directly againstdincli/sdk/runtime.py/dincli/sdk/cid.pyand the only differences are import paths and a docstring header.
Findings
No. 1 — systemic, repeated departure from the "zero behavior change" claim: every console.print in code that moved into dincli/sdk/* silently became a logger.* call. Non-blocking, but should be called out explicitly rather than left implied by "zero behavior change." ----- ** No Action Needed Santigo - Just flagging what behaviour changed **
This isn't a one-off — it's the same pattern in at least three places across the loader-tier commits:
dincli/sdk/ipfs.py::upload_to_ipfs— the three provider-branch status lines that wereconsole.print("[bold green]Uploading via Environment-backed IPFS...[/bold green]")(and the Filebase/custom equivalents) in the pre-movedincli/services/ipfs.pyare nowlogger.info("Uploading via Environment-backed IPFS..."). Confirmed by diffinggit show origin/develop:dincli/services/ipfs.pyagainstdincli/sdk/ipfs.py— every other message in that file already usedlogger.*before the move; only these three were rich-styledconsole.printcalls, and only these three changed character.dincli/sdk/manifest.py::get_manifest— the freshness-check warning, previouslyconsole.print(f"[yellow]Warning: Could not verify manifest freshness: {e}[/yellow]")incache_manifest's ancestor code (git show origin/develop:dincli/cli/utils.pyaround line 750), is nowlogger.warning("Could not verify manifest freshness: %s", e).dincli/sdk/config.py::get_env_key/set_env_key— the "{key}not found in.envfile" (was[bold red]), "python-dotenv not installed" (was[yellow]), and "Error saving to .env" (was[red]) messages are all nowlogger.warning.
None of these are functional regressions — dincli/sdk genuinely cannot import rich per the architectural constraint this whole PR exists to establish, so some conversion was unavoidable, and the CLI-facing wrapper cache_manifest in cli/utils.py still does its own console.print for the messages that matter for exit-code semantics (verified — see "What's good" above). But three concrete, user-visible things actually changed for these specific lines: they lose rich styling/color, they move from stdout to stderr (Python logging's default StreamHandler target), and they become gate-able by the user's configured log_level — previously these particular console.print calls fired unconditionally regardless of log-level config, so a user who has set log_level: WARNING or higher in their dincli config would now silently lose the "Uploading via..." status lines (INFO-level) that used to always print. At the default log_level: INFO all three still surface, just reformatted.
This is exactly the kind of thing the "zero behavior change, all call sites unchanged" claim in the loader-tier commit messages should not paper over. Recommend either amending those commit messages/PR description to scope the claim to "unchanged call sites and return values" (true) rather than "unchanged behavior" (not quite true for these log/console lines), or explicitly listing this console→logger conversion as a known, intentional side effect of the SDK boundary.
No. 2 — cache_manifest(..., info=True) now makes two on-chain RPC calls (getModel) where the old code made one. Non-blocking (minor inefficiency, not a correctness bug), but worth flagging since it's a real behavior change the "zero behavior change" claim doesn't account for.
Old cache_manifest (single function) fetched model_info = din_registry_contract.functions.getModel(model_id).call() once and used it both for the info print branch and the download/freshness check. The new split (dincli/sdk/manifest.py::download_manifest + get_model_info, glued back together by cli/utils.py::cache_manifest) calls download_manifest() (which may itself call getModel if the manifest is missing/stale) and then, independently, get_model_info() calls getModel again for the info dict. tests/test_sdk_manifest.py::TestCacheManifestCLIWrapper::test_info_flag_calls_get_model_info_and_prints actually asserts this double-call shape (download_calls == [5] and get_model_info_calls == [(5, True)] as two separate mock invocations), so it's tested — just not flagged as a divergence from the original single-RPC-call design. Any dincli ... --info command now does one extra eth_call round-trip. Low severity (RPC calls are cheap and idempotent reads), but real, and it's the kind of thing worth a one-line callout in the PR description rather than leaving under a "zero behavior change" umbrella.
No. 3 — serialize.py's uint256_string and address encoders don't validate their input; a negative int or a malformed address propagates either an unvalidated negative string or a raw ValueError (not a DinError) out of the SDK. Non-blocking follow-up, not a blocker for this PR.
Reproduced both by hand:
>>> _encode_dataclass(Outer(inner=Inner(val=5), items=[...], negative=-42, huge=2**256-1))
{'inner': {'val': '5'}, ..., 'negative': '-42', 'huge': '115792089237316195423570985008687907853269984665640564039457584007913129639935'}str(int(raw)) happily stringifies a negative number tagged uint256_string — there's no assertion that the value is actually in [0, 2**256). Given these values are expected to always originate from on-chain uint256 reads, this is unlikely to trigger in practice, but if it ever does (a bug upstream, a manually-constructed dataclass in a future daemon handler), the envelope would carry a semantically-invalid "-42" for a field the wire-protocol schema promises is a non-negative uint256 string, with no error raised anywhere.
>>> _encode_dataclass(Addr(a='not_an_address'))
ValueError: when sending a str, it must be a hex string. Got: 'not_an_address'A malformed address-tagged field raises a bare ValueError from Web3.to_checksum_address, not one of the DinError subclasses this same PR's errors.py establishes as the required shape for anything that crosses the SDK boundary. If to_envelope() is ever called with a dataclass containing a bad address (e.g. from a future daemon handler that didn't validate input upstream), the raw ValueError would propagate uncaught rather than becoming a structured {"code": ..., "message": ..., "details": ...} error — undermining the "every SDK-boundary error should be JSON-shaped" property the design review approved in §4.
Neither of these is exercised by tests/test_sdk_serialize.py — that file is happy-path only for these two encoders (valid, non-negative, well-formed inputs). Test coverage is otherwise decent (uint256 stringification, omit, checksum, enum-to-name, Decimal, nullable-tagged-None, and envelope success/error/null-data/correlation_id are all covered), but these two negative-input paths aren't.
No. 4 — two open asks from the design review (already flagged there, confirmed still unaddressed by this diff) — non-blocking follow-ups, not blockers for this PR.
- Amount-field tagging scan test: the design review (
review.mdin this folder, §2 close-out) asked for "a test that scans dataclasses for amount-shapedintfields and asserts they're tagged [metadata={"json": "uint256_string"}], same allowlist discipline §4a already applies to error details." No such test exists anywhere in this diff (grep -rln "uint256_string"across the tree hits onlydincli/sdk/serialize.pyitself and its own test file — no scanner). Still open; reasonable to defer to whenever the first real daemon-facing dataclasses land (this PR only ships the encoder + test fixtures, no production dataclasses to scan yet), but worth a tracking note so it doesn't quietly fall off. correlation_idorigin: the design review asked for a one-line clarification of wherecorrelation_idcomes from (generated per-call? tied to a job id?).Developer/design/sdk-interface-proposal.mdis unchanged on this point since that review (still just "optional for CLI, first-class for daemon jobs/logs" with no origin spec), andto_envelope()'s implementation just accepts whatever string the caller passes — it doesn't generate or attach one itself. Still open, still non-blocking (this is presumably PR #32's / the daemon's job to decide).
No. 5 — dincli/sdk/ipfs.py's # TODO(sdk-keystones): IpfsError is mislabeled: it isn't actually blocked on the deferred wallet/session/tx keystone, and it's a real gap for dind's retry logic today. Non-blocking, but should be re-scoped/actioned separately rather than left bundled with the private-key-sensitive work.
_raise_for_http_error (ipfs.py:89-98) and the requests.exceptions.RequestException catches in upload_to_ipfs/retrieve_from_ipfs (ipfs.py:183-184, 252-254) all raise a bare RuntimeError, tagged with a TODO(sdk-keystones): IpfsError comment implying the fix is gated on the same keystone work as wallet.py/session.py/tx.py (dincli/sdk/web3.py:14 has the equivalent TODO(sdk-keystones): NetworkError). But IpfsError already exists and is fully wired into the error taxonomy this same PR ships: errors.py:70 (class IpfsError(DinError): code = "ipfs_error"), and Developer/design/sdk-interface-proposal.md:281 explicitly calls out converting these raw RuntimeErrors into it. There's no dependency on DinSession/SignerProvider here — IpfsError doesn't touch signing at all, it just needs raise IpfsError(...) swapped in for raise RuntimeError(...) in the three spots above (plus an _ALLOWLIST entry in errors.py, e.g. {"provider": "str", "status_code": "int"}, so the detail sanitization the rest of errors.py enforces actually covers it).
This matters concretely for dind (PR #32): per errors.py's own module docstring, "the daemon maps [DinErrors] to job errors and keys retries off the stable code." A failed IPFS upload today raises a RuntimeError with no .code — dind's future job-retry policy has nothing structured to key off for "IPFS upload failed, should this job retry?" vs. any other unexpected exception. Recommend either wiring IpfsError now (it's a small, low-risk change fully within this PR's own architecture) or re-tagging the TODO so it doesn't read as blocked on the keystone task it isn't actually blocked on.
No. 6 — to_envelope() (§3's JSON envelope) has zero production call sites: nothing in either PR actually wires an SDK operation's dataclass return through it yet. Expected sequencing gap, not a regression — flagging so it's tracked explicitly rather than left implicit.
grep -rn "to_envelope" --include="*.py" across both worktrees turns up exactly two hits: the function's own definition in dincli/sdk/serialize.py and its test file, tests/test_sdk_serialize.py. No production code — not dincli/cli/*, not dincli/dind/* (PR #32) — ever calls it. Consistent with that: dincli/dind/jobs.py's JOB_HANDLERS registry has exactly one entry, "demo", and it's a no-op; and there is no --json flag anywhere in dincli/cli/ (grepped, no hits) for the CLI half of §3's "daemon always; CLI on --json" consumer-boundary design. So while to_envelope() itself is correctly built and tested (see "What's good" and No. 3 above), the pipeline that's supposed to produce the dataclasses it consumes — the sdk/operations/ layer from §8's package layout, calling real SDK functions for task registration, GI lifecycle, aggregator/auditor scoring, etc. — doesn't exist in either PR yet.
This is expected, not a bug: operations/ is explicitly one of the modules gated behind the deferred wallet/session/tx keystone (§8, "the remaining seven"), so there's nothing for it to wrap yet. Once that keystone lands and operations/ starts shipping real functions, dind will register real job handlers (client/auditor/aggregator role automation) that call into operations/ and route the resulting dataclasses through to_envelope() for the job queue and /health/logs — replacing today's single no-op "demo" handler. Recommend tracking this explicitly — added as BL-9 in Developer/BACK_LOG.md alongside this review — so "envelope built, nothing consumes it yet" doesn't quietly get lost as an implicit gap between the two PRs.
Recommendation
Mergeable as-is, modulo the deferred wallet/session/tx keystone (explicitly out of scope for this PR). Nothing here is a functional regression that breaks a call site — the four loader-tier shims I checked (cli/utils.py, cli/contract_utils.py, services/ipfs.py, services/runtime.py, services/cid_utils.py) all correctly re-export working code, state.py is verified byte-identical and ordinal-correct against the Solidity enum, and the full suite (144 tests) passes clean.
The one thing I'd push back on before merge is the wording of the commit messages / PR description: "zero behavior change" is not quite accurate for the loader-tier commits (No. 1, No. 2 above are real, if minor, behavior changes) — I'd ask for that language to be softened to something like "unchanged call sites and return contracts; console output for moved SDK-layer functions now routes through logging" so nobody downstream is surprised later. Findings No. 3 and No. 4 are legitimate gaps but genuinely low-stakes follow-up work, not blockers. No. 5 (the IpfsError TODO) is worth a quick fix or at least a re-scope before this lands, since it's cheap, self-contained, and directly feeds dind's retry logic — no reason to let it sit mislabeled as blocked on the keystone task. No. 6 (the unwired envelope/operations/ layer) isn't a blocker either — it's the natural next layer once the keystone lands, tracked in the backlog rather than left implicit.
…ne (issue #20 continued) Next task after task_220726_7 (both parts landed clean, PR #31 @ 6eac109, PR #32 @ 99f060b). Scopes DinSession/SignerProvider (§2) + wallet.py + tx.py's send() keystone (§5b) per the approved SDK interface proposal, folding in the nonce-management gap (BL-1) and the three other open §5b questions. sdk/operations/ stays explicitly out of scope, same as this task was deferred out of task_220726_7. Co-Authored-By: umeradl <umermajeed.cto@gmail.com>
to_envelope() (PR #31) has zero production call sites and dind's JOB_HANDLERS registry (PR #32) has only a no-op "demo" entry. Surfaced in the PR #31/#32 code review (finding No. 6) as an expected sequencing gap, gated behind the wallet/session/tx keystone (BL-2). Co-Authored-By: umeradl <umermajeed.cto@gmail.com>
Draft / WIP — progress on #20, not a merge-ready PR. Opened early per the request to keep feature-branch work visible. Base is
feat/din-sdk(notdevelop).What this contains
Incremental extraction of shared logic into
dincli/sdk/(issue #20), landed in reviewable slices:Developer/design/sdk-interface-proposal.md, v0.3) — SDK public interface + JSON output conventions; candidate seed for P3-DOC7. Direction confirmed by @umeradl; contract not yet frozen.dincli/sdk/{cid,errors}.py+ a fresh-subprocess import-boundary test.dincli/sdk/{config,log,web3}.py→{ipfs,contracts}.py→{manifest,runtime}.py, with the original modules (cli/utils.py,cli/log.py,services/{ipfs,runtime}.py,cli/contract_utils.py) reduced to backward-compat re-export shims.task_220726_7Part A) —dincli/sdk/state.pyanddincli/sdk/serialize.py(details below).The
dincli/sdkpackage now holds a dependency-clean foundation:cid, errors, config, log, web3, ipfs, contracts, manifest, runtime, state, serialize. An import-boundary test enforces the core rule —import dincli.sdkpulls in notyper,rich, ordincli.cli.*(CLI depends on SDK, never the reverse).Latest slice —
state.py+serialize.py(task_220726_7Part A)dincli/sdk/state.py— GI-state converters (GIstateToDes/GIstateToStr/GIstatestrToIndex) and their backing data moved verbatim fromcli/utils.py, which is now a re-export shim (call sites unchanged). Adds aGIStateIntEnum (0-based, matching the Solidity enum ordinals) and twovalidate_*predicates lifted fromDinContext's GI-state guards, raising the SDK'sValidationError. The twocontext.pyguards now delegate the decision to these predicates while keeping identical console output + exit codes — no CLI behavior change.dincli/sdk/serialize.py—to_envelope()+ a metadata-driven JSON encoder per proposal §3: the{status, data, error, meta}envelope (withschema_version/sdk_version) and the encoder rules —uint256_string→ decimal string,omit, small-int passthrough,bytes/HexBytes→0xhex,address→ checksummed,enum→ name,Decimal→ string,None→ null (except taggedomit).Verification
state/serializetests: one per encoder rule + both envelope paths, converter snapshot + shim-identity + predicate checks).stateandserializeimport clean oftyper/rich/dincli.cli.*— green across all 11 SDK modules.dincli --help+ full-package import smoke pass through the shims (behavior parity).tests/dincli/*, needs the Hardhat/Foundry compile harness) and the Torch-dependent DP test (tests/test_cache_client_dp.py).Scope vs #20 (explicitly partial)
serialize.py) · ✅ SDK tests · ✅ interface/back-compat documentedoperations/role layer) — not started (depends onwallet.py/session.py)wallet.py,session.py/DinSession,SignerProvider§2,tx.send()§5b). Split out deliberately: private-key/keystore/signing logic warrants its own focused review pass (flagged for @umeradl given the crypto sensitivity), not a shared slot with unrelated work.tx.send()also needsDinSession, which doesn't exist yet.So this does not close #20; it's the extraction foundation the
operations/layer and the signing keystone build on.Note on the diff
Base branch
feat/din-sdkis behinddevelop, so the file diff includes thedevelop-sync commits (P3 design docs, upgradeable-proxy/ProxyAdmin work, task files). The SDK-specific commits are thedocs(design)proposal,feat(sdk)seed + serializer, and therefactor(sdk)loader/state extraction commits.