Asset-rights NFT stack: mock + EVM mint registry for sealed .mass releases - #391
Asset-rights NFT stack: mock + EVM mint registry for sealed .mass releases#391ibuilder wants to merge 4 commits into
Conversation
…tep 4) Introduce a ChainProvider protocol with a deterministic mock implementation, a release_tokens Postgres table keyed on content_hash, and API routes to mint, look up, and list release tokens. Chain minting is off by default (AEC_CHAIN_ENABLED) and validates sealed manifests before recording a mock mint. No RPC, wallet, or Solidity toolchain yet — step 5 can swap in a real provider. Co-authored-by: Matthew M. Emma <ibuilder@users.noreply.github.com>
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Add MassRelease721 (Foundry + OpenZeppelin ERC-721) binding each sealed release to a bytes32 content_hash, an EvmChainProvider via web3.py, mock/Pinata IPFS storage for ERC-721 metadata, auto-pin on mint, and registry/on-chain verify. Hardening: strict content_hash validation, idempotent mint with IntegrityError race handling, gas estimation for EVM txs, expanded test_asset_chain (42 checks), forge test CI job, and web3 added to requirements.lock. Co-authored-by: Matthew M. Emma <ibuilder@users.noreply.github.com>
MERGED origin/main (33 commits, v0.3.1133 -> v0.3.1143, zero conflicts). That alone fixed two of
the four API-gate failures the old head had: 656/660 -> 658/660 locally.
1. LOCKFILE — provenance, not dependencies. `pip-compile --generate-hashes` failed with "stale".
The committed lock was generated as `--output-file=requirements.lock.new requirements.in` from
inside services/api/, so its header recorded the wrong command and all 11 annotations read
`# via -r requirements.in` instead of `# via -r services/api/requirements.in`. Regenerated with
CI's exact command on Python 3.12 (the prod base image's version; on 3.11 numpy==2.5.2 will not
even resolve). 84 lines changed and ZERO of them a version or a hash — measured, not assumed:
`diff | grep -E '^[<>] [a-z0-9_.-]+=='` is empty. The dependency set was always right; only its
record of where it came from was wrong.
2. ALEMBIC — a real model/schema divergence, and the gate was right to fail.
`models.py` declares `content_hash: mapped_column(String, unique=True, index=True)`, which
SQLAlchemy renders as ONE unique index `ix_release_tokens_content_hash`. The migration instead
wrote a named `UniqueConstraint("content_hash", name="uq_release_tokens_content_hash")` AND a
NON-unique index — so the database carried a constraint the model does not declare and lacked
the uniqueness the model does. `alembic check` reported three pending operations. Migration now
matches the model. The guarantee is identical (Postgres enforces a unique index exactly as a
unique constraint); what changes is that the schema stops having drifted from its mapping.
3. ROUTE REACHABILITY — two new unreachable routes, frozen with an expiry condition.
`/asset-rights/mints` and `/projects/{pid}/asset-rights/mints`. This PR touches NO apps/web file,
so "no client caller" is the accurate state of a staged feature, not an oversight. Recorded with
the part that matters: FOUR routes were added and the rule can only see TWO. `POST
/asset-rights/mint` (leaf `mint`) and `GET /asset-rights/mints/verify` (leaf `verify` — the
ASSET-VERIFY blind spot, met a second time) are equally uncalled and simply invisible to it.
Freezing two does not make the other two reachable, and a reader assuming otherwise would be
wrong.
STILL RED, and I cannot close it from here: `test_actions_pinned`. #391 adds
`foundry-rs/foundry-toolchain@v1` to ci.yml, and that gate requires third-party actions to be
pinned to a 40-hex commit SHA because a tag is mutable. Resolving the real SHA needs read access to
foundry-rs/foundry-toolchain, which this session is not scoped for. A fabricated SHA would satisfy
the regex while pointing at nothing, so it is left failing and raised instead.
Verified: full backend suite 658/660 (the two above; the reachability one now fixed, so 659/660 on
this tree), ruff clean, test_lock_satisfies_requirements / test_declared_imports /
test_alembic_single_head / test_alembic_migrations all green.
Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tt2XKB83wwNt2nrMbK6eEA
|
Picked this up with the owner's approval of
The lockfile was never wrong about dependencies. It was generated as The alembic failure was a genuine defect and the gate was right. On the two frozen routes — the part worth reading. ⛔ What I can't do from here
Resolving the real SHA needs read access to Everything else verified: backend suite 659/660 on this tree (the remaining one being the pin above), Generated by Claude Code |
Summary
Completes the asset-rights / NFT stack for sealed
.massreleases — steps 4 and 5.Each sealed release (
asset_rights.json+content_hash) can now be registered as a mock or on-chain ERC-721 token, with optional IPFS metadata pinning.Step 4 (previous commit)
ChainProviderprotocol +MockChainProviderrelease_tokensPostgres table (keyed oncontent_hash)POST /asset-rights/mint, lookup, project list routesStep 5 (this commit)
Smart contract (
services/chain/)MassRelease721— OpenZeppelin ERC-721, one token percontent_hash, owner-only mintMASS_RELEASE721_OWNER=0x… forge script script/DeployMassRelease721.s.sol --broadcastforge install+forge testPython providers
evm_provider.pyweb3.py+ JSON-RPCipfs_storage.pyrelease_nft_metadata.pyHardening
sha256:<64 hex>validation on all mint pathspin_metadata=true(default)IntegrityErrorrace handlingGET /asset-rights/mints/verify— registry vs on-chain cross-check/asset-rights/statusnow reportschain.ipfsandchain.evmreadinesstest_asset_chain.pyConfiguration
Mock (default — no network):
EVM (testnet/mainnet):
IPFS (optional — auto-pin on mint):
Test plan
python3 test_asset_chain.py(42 checks)forge testinservices/chain/test_alembic_single_head.py