Skip to content

feat(cli): phase-creator command tree with offline metadata validation (spike 661) - #580

Draft
ExcelDsigN-tech wants to merge 21 commits into
PHASE-STELLAR:mainfrom
ExcelDsigN-tech:feat/creator-cli
Draft

ExcelDsigN-tech wants to merge 21 commits into
PHASE-STELLAR:mainfrom
ExcelDsigN-tech:feat/creator-cli

Conversation

@ExcelDsigN-tech

Copy link
Copy Markdown

The flag is phase-165 because upstream #573 assigned phase-161 to the typed contract-error taxonomy. Set FEATURE_PHASE_165=1 to enable it.

Summary

Adds phase-creator, one CLI for creators who never open the web app. It scaffolds a collection, validates it offline against the exact on-chain rules, pins assets headlessly, checks the env, and deploys to testnet. Output is CI-friendly: --json, --dry-run, --network, and exit codes 0–5. Everything is behind phase-165 (default off).

Changes

  • lib/creator-cli.ts, scripts/phase-creator.cts, bin: phase-creator, npm run creator --: scaffold, validate, env, pin, deploy, run.
  • lib/creator-metadata-rules.ts: a byte-exact port of str_is_safe_for_json_fragment plus the price rule, with shared parity vectors in contracts/phase-protocol/test-vectors/ read by vitest and src/test.rs.
  • lib/ipfs-upload-retry.ts: resolvePinataJwt (now also used by POST /api/ipfs) and scrubSecret.
  • lib/env-validation.ts: diagnoseCreatorEnv, reused by diagnose-env.ts.
  • scripts/creator-preflight.ts: thin-wrapper preflight called before main() in the ten listed scripts. --dry-run prints the plan and exits 0. The flag-on env check exits 3. Flag off, it's a no-op.
  • scripts/proof-creator-661.ts (npm run proof:661): live proof runner.

Behavior notes

  • The contract accepts non-ASCII, so the validator warns on it instead of rejecting it (the issue asked for rejection; see the spike doc).
  • Found: validateCollectionImageUri counts UTF-16 units while the contract counts UTF-8 bytes. Documented with a test, not changed here.

Flag and rollback

FEATURE_PHASE_165 / NEXT_PUBLIC_FEATURE_PHASE_165, default off. Rollback: unset it. Side-effecting commands exit 5, wrapped scripts behave exactly as before, and nothing is persisted.

Tests

  • lib/__tests__/creator-metadata-rules.test.ts (27), creator-cli.test.ts (14), creator-preflight.test.ts (2).
  • tsc --noEmit: 0 errors. vitest run: all green with flags off.
  • Rust: the shared-vector test passes against str_is_safe_for_json_fragment extracted verbatim. The crate itself doesn't compile on main until the PhaseError discriminant collision is resolved (docs/investigations/main-baseline-2026-10-17.md).

Live proof

Checklist and placeholders: docs/spikes/661-creator-cli-findings.md, "Live proof checklist". Live proof file: pending. The testnet deploy is blocked by the PhaseError numbering question in #574.

Follow-up

Resumable pin-then-deploy, contract-ID drift detection, folding the script logic into the CLI, the UTF-16 validator fix, and removing the hardcoded IDs in setup_token.ts. Details are in the spike doc.

Depends on

#574 (chore/fix-main-baseline). Merge that first; until it merges, this diff also shows its commits.

Closes #524

PhaseFeatureFlag lost phase-146..153, phase-editions and phase-timed-drops,
and SecurityCounterName lost ~50 counters used by merged spikes. Restoring
them clears 96 of the 120 tsc errors on main. Also registers phase-159,
phase-161, phase-162 and phase-163 for spikes 659, 661, 662 and 663.
…chamber modules

0d14b9a ("fixed") re-encoded several files as cp1252-decoded UTF-8 and
prepended a BOM plus // @ts-nocheck. User-facing strings in the chamber
(e.g. "Transacción", "…") shipped corrupted, and a few glyphs (●, ”)
were lost outright. Files with no later commits are restored from
0d14b9a^; files edited since get only their mojibake comments repaired.

60001c5 (PHASE-STELLAR#33 refactor) moved ChamberLogStream, ChamberCatalogThumb,
PhaserLiqTokenLink and the chamber chrome classes out of fusion-chamber
into components/chamber/* but never committed those files, and left
five one-word "placeholder" files behind. The four modules are recreated
verbatim from the code it deleted; the unused placeholder imports and
files are removed.
- offers accept route: spike 674 collab-split fan-out (3e4b562), with the
  splits binding hoisted so the response can actually read it
- IpfsDisplayImg: `fallback` prop destructure (b89c4f2)
- wash-screening: washScreenReasonForEdition (3e4b562)
- viewer-signature: viewerSignatureShort (f9a08b6; removed by 7cc03e4
  while three components still import it)
- sqlite-db: exhibitions, price_suggestions, moderation_queue and
  quest_arcs tables (b89c4f2). Their absence failed 27 tests.
Real bugs the type errors were hiding:
- nft-index subscribe: validateWebhookUrl accepted https://127.0.0.1 and
  every 172.16/12, 100.64/10 and 0/8 literal (SSRF). Now a proper IPv4
  range check.
- lib/stellar.ts settle verification read meta v1/v3 only and called
  ContractEventV0.contractId(), which no longer exists, so every settle
  check fell through to SETTLE_EVENT_NOT_FOUND. It now reads meta v4
  (protocol 23) and v3 and takes the emitter from ContractEvent.
  Still fails closed on any mismatch.
- artist-attestation passed a string where an ScAddress is required and
  decoded an already-decoded retval, so attestation never read true.

Also: winback route called a nonexistent addNotification (now
createNotification with a winback_reactivation type), wallet-provider
declared `controller` twice, duplicate `ok` keys in ve-lock, and
Buffer/Uint8Array typing in the ipfs and avatar routes. wallet-provider
and stellar.ts are also restored from the 0d14b9a encoding damage.
- artist-profile-client imported describe/it from node:test, so vitest
  found no suite
- world-roles-rbac seeded worldRoles.json while the store reads
  world-roles.json (serverDataJsonPath("worldRoles"))
- avatar-provenance hashed a string through the bytes-only sha256Hex;
  encoding first gives the same digest
- forge-pipeline imported bun:test; the APIs it uses are identical in vitest
- viewer-authorization: Buffer typing for Keypair.sign
eslint.config.mjs imports eslint-config-next, which was never installed,
so npm run lint failed outright. dotenv is imported by scripts that the
root test suite loads (scripts/reset-phase.ts).
- ethnum 1.5.2 -> 1.5.3 in both lockfiles: 1.5.2 fails to compile on
  rustc 1.97 (E0512 transmute), which broke cargo test for both crates
- mock-token test called an associated fn as a method outside a contract
  context; it now goes through the generated MockTokenClient

phase-protocol still does not compile: see the baseline notes. Three
merged spikes each claimed PhaseError discriminants from 18 upward.
tsc is at 0 errors and vitest is green on this branch, so both can gate
PRs now. docs/investigations/main-baseline-2026-10-17.md records what was
fixed and what is blocked (phase-protocol discriminant collision).
…lient)

lib/stellar-wallet-kit.ts is loaded by the wallet provider on every route
and imported randomUUID from node:crypto. The client polyfill has no
randomUUID, so hydration threw and nothing interactive rendered. The nonce
path is browser-only, so it now uses globalThis.crypto.randomUUID().
…ASE-STELLAR#524)

- lib/creator-cli.ts + scripts/phase-creator.cts (bin phase-creator,
  npm run creator --): scaffold, validate, env, pin, deploy, run with
  --json, --dry-run, --network testnet and exit codes 0-5. Side effects
  need FEATURE_PHASE_161; validate, env and dry runs do not
- lib/creator-metadata-rules.ts: byte-exact port of
  str_is_safe_for_json_fragment plus the price rule, issues tagged
  chain (with PhaseError PHASE-STELLAR#9/PHASE-STELLAR#10), app or warn. The contract accepts
  non-ASCII, so it is a warning, not a rejection
- shared parity vectors in contracts/phase-protocol/test-vectors/, read
  by vitest and by new Rust tests in src/test.rs
- headless pin through pinFileToIpfsWithRetry; the JWT lookup moves to
  resolvePinataJwt (used by POST /api/ipfs too) and is scrubbed from
  all output
- diagnoseCreatorEnv in lib/env-validation.ts, reused by diagnose-env.ts
- deploy and run delegate to the existing scripts via an allowlist
- clear require() lint errors in the touched files

Finding: lib/phase-protocol.ts validateCollectionImageUri counts UTF-16
units while the contract counts UTF-8 bytes; documented with a test.
… (spike 661)

- scripts/creator-preflight.ts, called right before main() in the ten
  listed scripts: --dry-run prints the plan and exits 0 before any side
  effect; with FEATURE_PHASE_161=1 it runs `phase-creator env` first and
  exits 3 on a missing or malformed variable. Flag off and no --dry-run,
  it returns immediately. Script logic is unchanged.
- scripts/proof-creator-661.ts (npm run proof:661) records each CLI step's
  --json output under .data/proofs for the maintainer's live run
- spike doc rewritten against the real issue: five questions, acceptance
  criteria status, live checklist with placeholders, follow-up list
- docs/spikes/PR_661.md
SEP-53 wallets sign SHA256("Stellar Signed Message:\n" + message). Signals,
world roles, avatar ownership and gated-preview viewer auth verified the
raw prefixed bytes instead, so every real Freighter signature was rejected
with "Invalid signature: payload not signed by this wallet". The unit
tests passed only because they signed the same raw bytes.

- lib/viewer-signature.ts: sep53SignedBytes + verifySep53Signature, used
  by verifySignalSignature, verifyWorldRoleSignature and the avatar route
- lib/viewer-authorization.ts: viewerSignatureBytes is now the SHA-256
  digest the wallet's key actually signs
- tests sign like a wallet; the official SEP-53 spec vectors are a
  regression test; tests/viewer-authorization.test.ts used an address
  unrelated to its seed and could never pass
Resolve lib/feature-flags.ts: keep the restored phase-146..153, editions
and timed-drops members alongside upstream phase-158..161. The spike flags
move to their own branches.
Upstream PHASE-STELLAR#573 assigned phase-161 to the typed contract-error taxonomy.
…creator-cli

Keep both flag rows in the docs; feature-flags.ts takes the baseline and adds phase-165.
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.

# Spike 661 — Creator CLI with scaffold, metadata validation, pin and deploy

1 participant