원클릭으로
factory-flashing
SEC.3 Factory Flashing pipeline — provision per-device keys into STM32/SE05x at manufacture. Read SSOT docs first.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
SEC.3 Factory Flashing pipeline — provision per-device keys into STM32/SE05x at manufacture. Read SSOT docs first.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Use when working on the silken_net frontend — the Phlex component tree (app/views/components/ + shared/ui|iot|web3/), the Tailwind v4 @theme design-token system (gaia-* surfaces/text, status-*, token-* blockchain, gaia-input-*), the ApplicationComponent base (the tokens() TailwindMerge wrapper + class-autoscoped t() i18n + Turbo-broadcast-safe delegated helpers), the 8 Stimulus controllers, and Turbo Streams/Frames. Knows the non-obvious gotchas — NO tailwind.config.js (SSOT = app/assets/tailwind/application.css @theme), raw Tailwind forbidden in shared components (use status-*/gaia-* tokens), register a new font-size in CUSTOM_TEXT_SCALE for TailwindMerge, NO DB queries in a Phlex initialize, t('.key') autoscopes by class name, specs default to English, Turbo broadcast_* runs in model context (no current_user). Routes to 04_04 (the design-system SSOT) + CLAUDE.md §6, does not restate. Examples: 'add a Phlex component', 'add a design token', 'why isn't my color class merging', 'add a Stimulus controller', 'i1
Use when working on the silken_net telemetry / Proof-of-Growth pipeline — the uplink→verification→minting flow (CoAP intake → UnpackTelemetryWorker / TelemetryUnpackerService → IoTeX verify → Chainlink oracle → mint), the Sidekiq strict-priority queues, TelemetryLog (RANGE-partitioned, KENOSIS — validations live in valid_sensor_data?, not the model), and the dual-computation integrity (server Float Lorenz ≡ firmware mruby). Knows the gotchas — DID=0 in a batch is DEAD (ARCH.54: Queen pulse rides the signed QATT-v2 header → enqueue_envelope_health, both eras drop DID=0), oracle_status_*? enum methods, strict queue drain (uplink fully before alerts), find_with_partition_pruning. Routes to CLAUDE.md §5/§6 + the 05_02 canon, does not restate. Examples: "add a telemetry field", "change minting logic / guards", "why is an alert delayed", "decode the uplink packet", "why does server Z differ from device Z".
Use when working on the silken_net Web3 / on-chain surface — the 12-chain Proof-of-Growth pipeline (app/services/ + workers): SCC/SFC Solidity contracts, batchMint + Binary-Search poisoned-record isolation, the BlockchainTransaction AASM (incl. manual_review double-spend guard), minting guard-clauses (IoTeX / Chainlink / Hadron KYC), Dynamic Tax, slashing / penalty-factor de-correlation, Solana micro-rewards (Ed25519, batch payouts), DAO / Governor / Timelock, WEB3_STRICT_MODE. Routes to CLAUDE.md §1 (12-chain overview) + §6 (web3 gotchas) + the 05_01..05_06 canon (solc One-Home = 05_03), does not restate. Examples: "add a chain integration", "change the minting threshold", "why is a tx stuck in manual_review", "batchMint reverts on dry-run", "edit the slashing penalty", "Solana reward formula / batch payout".
Use when working on the silken_net Rails 'Web2 core' — data models (app/models/ + concerns), the REST API v1 controllers (app/controllers/api/v1/), auth/RBAC (Bearer + salt-bound session cookie, M2M Ed25519, Pundit policies), the non-money services/workers (app/services/, app/workers/), and the MaintenanceRecord / Evidence-Protocol domain. Knows the non-obvious gotchas — role enum prefix:true (role_admin?, NOT admin?; no 'patrol' role exists), the IDOR sibling-guard for client-supplied FKs (foreign-but-existing → 404, missing → 422), the session[:ps] password-salt stamp, M2M token = full org-admin scope (SEC.16), Idempotency-Key → 400 not 422, the webhook HMAC fail-closed pattern, exact HKDF info-strings per owner type, Gateway#online? = config_sleep_interval_s * 1.2, self.primary_key = 'id' on partitioned models, Auditable = after_update_commit + saved_change (NOT AASM after_all_transitions), the FactoryBot initialize_with reuse for Tree's auto-created wallet/calibration. Routes to the 04_01/04_02/04_03/04_0
Use when working on the SSOT docs (docs/NN_NN_*.md) — editing or creating a canon doc, hunting or fixing SSOT drift, adding a docs linter / CI gate, checking where a fact canonically lives, or publishing canon to the GitHub wiki. Operational playbook for docs:check_refs / docs:toc / tracker:check / wiki:sync; defers the STANDARD itself to 00_02 + 00_06. Examples: "edit 03_05", "is this value consistent across the docs?", "add a drift linter", "publish the docs to the wiki", "where does the Lorenz constant live?"
Use when curating the persistent file-based memory (…/memory/MEMORY.md + per-fact *.md) — structuring/grouping the index, removing cruft or duplication, fixing stale index hooks, trimming a bloated index line, or ensuring open action-items in memory are also tracked in 00_07. The HOW lives in .claude/prompts/memory_housekeeping.md; the memory FORMAT standard lives in the system prompt (don't restate). Iron rule: NO AMNESIA — preserve wins over cleanup. Examples: "почисти память", "поструктуруй memory", "повидаляй дублі в памяті", "memory housekeeping", "чи всі to-do з памяті є в 00_07?"
| name | factory-flashing |
| description | SEC.3 Factory Flashing pipeline — provision per-device keys into STM32/SE05x at manufacture. Read SSOT docs first. |
Burns per-device keys into a Soldier/Queen at manufacture, then locks the chip (RDP). Navigation aid — the SSOT is the code + docs below; this skill points, it does not restate.
| Document | What it covers |
|---|---|
docs/03_06_Factory_Flashing_and_Key_Provisioning.md | THE factory home (split from 03_05 §3.4): pipeline Гілки A/B, HKDF per-device derivation, K_ota, §5 ops-security (2-Person Rule, master-key delivery variants, SEC.3 status) |
docs/03_05_Hardware_Symmetric_Crypto_and_Security.md | Crypto modes, SE050 (SEC.6), key rotation, RDP (SEC.2) |
docs/00_07_Action_Plan_Tracker.md | SEC.3 (bench SWD + Bitwarden live — residuals), SEC.2 (RDP Level 2) |
Entry: lib/tasks/factory.rake → [FW.54] one-pass UID→DID at factory:flash:
for a Tree the device_uid arg = 24-hex silicon UID (NOT a DID) →
SilkenNet::DidDerivation.wire_did_from_uid_hex → TreeResolver.resolve!
(create with CLUSTER_ID+TREE_FAMILY_ID env / re-flash / bind legacy /
DID-collision → CollisionError = quarantine, 03_01 §7); the session's
device_uid = derived wire-DID. Bare SNET- DID accepted only for a Tree
that already has trees.silicon_uid_hex; Gateway path unchanged.
Then FactoryFlashing::Session.run (after supervisor-approved). One ActiveRecord::Base.transaction:
may_start? + device exists + master key fetched into @master_key (fail fast before the tx; the result is NOT discarded — SEC.3 DI).CommandBuilder.preflight_commands (connect + -r32 0x1FFF7590 12) runs FIRST; live mode parses stdout via UidReadout and compares the board's UID to trees.silicon_uid_hex before any derivation or -w32 — mismatch/unparseable → WrongBoardError (not even a HardwareKey row materializes). dry-run or passport-less device → skip.MasterKeySource (Env or Bitwarden adapter); WeakKeyDetector refuses a weak key. The fetched key threads as master_key: param into every derivation below (runtime callers of the same services use the ENV fallback instead).HardwareKeyService.provision(device, master_key:) (the SINGLE HKDF source — same derivation the firmware runs; never derive keys elsewhere).SecureElementProvisioner emits the I²C ATCA write-zone transcript.CommandBuilder#flash_commands (key writes + RDP + disconnect; connect/UID-read already ran as preflight).Executor (dry-run prints; --execute spawns subprocesses).AuditTrail.record! → chain-hashed AuditLog (metadata incl. silicon_uid_hex) + MaintenanceRecord; complete! (or fail_with! + rollback).| Component | Role |
|---|---|
factory_flashing/session.rb | Orchestrator (run, preflight!, verify_silicon_uid! wrong-board guard, AASM start!/complete!/fail_with!) |
factory_flashing/tree_resolver.rb | [FW.54] UID→DID→Tree: create / re-flash / bind / collision→quarantine; deliberately does NOT enqueue peaq (offline factory) |
factory_flashing/uid_readout.rb | [FW.54] tolerant -r32 stdout parser (keyed on 1FFF7590); live format = bench-confirm (RUNBOOK 1.3) |
factory_flashing/command_builder.rb | STM32_Programmer_CLI emission (preflight_commands class-method: connect+UID-read; flash_commands per гілка, write_block, rdp_command) |
factory_flashing/secure_element_provisioner.rb | ATECC608B data-zone provisioning (Гілка B) |
factory_flashing/executor.rb | dry-run vs live subprocess (programmer_available?) |
factory_flashing/master_key_source.rb | Base / EnvAdapter / BitwardenAdapter master-key fetch — the fetched key feeds HKDF via Session (SEC.3 DI), not just the preflight gate |
factory_flashing/audit_trail.rb | chain-hashed audit log record |
ota_hmac_key_service.rb | per-cluster OTA HMAC key fetch_for(cluster_id, master_key: nil) (Гілка A KOTA block + Гілка B ATECC provisioning) |
Line numbers drift every commit — grep/read
Sessionfor live locations rather than a hardcoded table ([[feedback_no_volatile_counts]]).
-w32). B = Гілка A + SE05x identity-chip: KEYL still goes to Protected Flash in BOTH branches (03_06 §1); the SE adds only the Ed25519 voice / cert / anti-clone serial, NOT the LoRa key (SEC.14 = provisioning-only). ⚠️ The current gilka_b_commands is still the legacy ATECC-model (skip-key-writes, only RDP-lock + disconnect) — a known code-lag pending the SE050 eval-kit (00_07 SE050-MIGRATION); a Гілка-B unit flashed by today's code would have no Flash KEYL → brick.CommandBuilder addresses/magics mirror firmware/soldier/main.c FLASH_KEY_ADDR (post-ARCH.42): KEYL(0x4B45594C)+aes@0x0803E000, LSED(0x4C534544)+k_seed@0x0803E014 (Tree), KEYC(0x4B455943)+coap@0x0803E040, EDSK(0x4544534B)+ed25519_seed@0x0803E064 (Gateway, L1 QATT), KOTA(0x4B4F5441)+k_ota@0x0803E800 (Tree, FW.23), KEYB(0x4B455942)+bcast@0x0803E828 (Tree, FW.2 (в) cluster control-plane). Drift here ⇒ device can't read its own key. Change one side → change both + host tests. Word→BE-bytes convention (FW.30): firmware unpacks each -w32 word MSB-first — naive memcpy on LE Cortex-M4 reverses every word.STM32_Programmer_CLI execution + RDP Level 2 (SEC.2, irreversible chip lock) are bench-gated, not yet run on hardware.K_seed; Gateway: 64-hex AES-256 CoAP key + 32-hex broadcast key written to the LoRa KEYL slot (FW.2 (в), post-2026-07-03 — Queen's single control-plane key = the KEYB broadcast value; the pre-fix «LoRa slot unused» bricked her at boot) + optional 64-hex Ed25519 seed (L1 QATT «голос Королеви» — generated by Session on the factory host, NOT HKDF; only the pubkey persists in HardwareKey). CommandBuilder#validate! enforces this.AuditLog (the chain stays intact).Session refuses to run unless the ProvisioningSession is supervisor_approved (preflight may_start?).silken-net-test-master-key-32b!!) is itself a placeholder needle in the detector, so validating inside hkdf_derive/fetch_for/derive_seed fails the whole suite. Coverage is already two-layer by design: EnvAdapter guards the factory path, the boot initializer (master_key_strength_check.rb) guards runtime.%08X words in register order (0x1FFF7590 first), exactly how firmware did_derive.h reads them; golden pair 0039002F3138511538323634 → SNET-80B12004 frozen in did_derivation_spec ↔ firmware/test/test_soldier_logic.c. Reordering/re-endianing the parse = a different DID on backend vs silicon (keys diverge silently).wire_did_from_uid_hex feeds both the factory (TreeResolver) and the field ProvisioningController#register (the old last(8)-suffix DID + the dead tree double-init guard were a real prod bug, fixed 2026-07-03). Never invent a third derivation path.silicon_uid_hex = birthday collision or wrong chip → CollisionError, unit goes to quarantine (03_01 §7); same UID = legit re-flash (idempotent no-op).Session — orchestrator callers/calleesfactory flashing across app/services/ — related flowscommand_builder.rb for the exact CLI sequence; 03_06 §5 for the threat model