"TEN Singapore HoldCo flip CLI — `cyberos-ten holdco-flip` orchestrates ACRA filings + shareholder migration + ESOP transfer for VN → SG corporate restructure"
§1 — Description (BCP-14 normative)
The TEN service MUST ship HoldCo flip CLI at services/ten/src/holdco/ + services/ten/src/cli/holdco_flip.rs orchestrating 6-step restructure with triple-sign + immutable checkpoints, 4 memory audit kinds.
- MUST validate
holdco_flip_stepagainst closed enum per DEC-2401.
- MUST require CEO + CFO + CLO triple-sign at initiation per DEC-2403 via
triple_sign_gate.rs::can_initiate(flip):
- All three signed
- Same-person rejected across any two slots
- Initiation proceeds only after all three
- MUST orchestrate 6 steps via CLI per DEC-2400:
- sg_entity_form: BizFile API call to form Pte Ltd
- acra_filings: generate Form 24 + ACRA submissions
- shareholder_migration: VN shareholders → SG shareholders (signed agreements track)
- esop_reissue: TASK-ESOP-001 grants re-issued under SG entity
- residency_migrated: TASK-TEN-103 tenant residency → sg-1
- completed: all steps done
- MUST be resumable per DEC-2402 — each step writes checkpoint; restart resumes from last completed.
- MUST define tables at migration
0010: ```sql CREATE TABLE ten_holdco_flips ( flip_id UUID PRIMARY KEY, tenant_id UUID NOT NULL, status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','sg_entity_formed','acra_filings_prepared','shareholder_agreements_drafted','esop_reissued','residency_migrated','completed','failed')), ceo_signed_by UUID, ceo_signed_at TIMESTAMPTZ, cfo_signed_by UUID, cfo_signed_at TIMESTAMPTZ, clo_signed_by UUID, clo_signed_at TIMESTAMPTZ, initiated_at TIMESTAMPTZ, completed_at TIMESTAMPTZ, failure_reason TEXT, trace_id CHAR(32), created_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE (tenant_id) -- one flip per tenant ); ALTER TABLE ten_holdco_flips ENABLE ROW LEVEL SECURITY; CREATE POLICY flips_rls ON ten_holdco_flips USING (tenant_id = current_setting('auth.tenant_id')::uuid) WITH CHECK (tenant_id = current_setting('auth.tenant_id')::uuid); REVOKE UPDATE, DELETE ON ten_holdco_flips FROM cyberos_app; GRANT UPDATE (status, ceo_signed_by, ceo_signed_at, cfo_signed_by, cfo_signed_at, clo_signed_by, clo_signed_at, initiated_at, completed_at, failure_reason) ON ten_holdco_flips TO cyberos_app;
CREATE TABLE ten_holdco_flip_steps ( step_id UUID PRIMARY KEY, tenant_id UUID NOT NULL, flip_id UUID NOT NULL REFERENCES ten_holdco_flips(flip_id), step_name TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','running','completed','failed')), started_at TIMESTAMPTZ, completed_at TIMESTAMPTZ, output_jsonb JSONB, failure_reason TEXT, UNIQUE (flip_id, step_name) ); ALTER TABLE ten_holdco_flip_steps ENABLE ROW LEVEL SECURITY; CREATE POLICY flip_steps_rls ON ten_holdco_flip_steps USING (tenant_id = current_setting('auth.tenant_id')::uuid) WITH CHECK (tenant_id = current_setting('auth.tenant_id')::uuid); REVOKE UPDATE, DELETE ON ten_holdco_flip_steps FROM cyberos_app; GRANT UPDATE (status, started_at, completed_at, output_jsonb, failure_reason) ON ten_holdco_flip_steps TO cyberos_app; ```
- MUST expose CLI: ``
text cyberos-ten holdco-flip init --ceo-sign --cfo-sign --clo-sign cyberos-ten holdco-flip resume --flip-id <id> cyberos-ten holdco-flip status --flip-id <id>``
- MUST emit 4 memory audit kinds per DEC-2404. PII per TASK-MEMORY-111: output_jsonb SHA256.
- MUST thread trace_id from CLI → orchestrator → step → audit.
- MUST NOT initiate without triple-sign per DEC-2403.
- MUST NOT mutate prior step per DEC-2402.
- MUST NOT allow same-person dual sign across slots per DEC-2403.
§2 — Why this design
Why CLI (DEC-2400)? HoldCo flip is rare + high-stakes; CLI provides reproducible scripting; full audit trail.
Why 8-state enum (DEC-2401)? Captures sequential restructure phases; status visible to ops.
Why triple-sign (DEC-2403)? Restructure involves $100k+ in legal + restructure costs; broad governance.
Why ACRA filings (DEC-2400)? SG company formation requires statutory submissions; CLI generates standard package.
§3 — API contract
CLI usage:
$ cyberos-ten holdco-flip init
✓ CEO signature captured
✓ CFO signature captured
✓ CLO signature captured
✓ Flip initiated (flip_id: abc-123)
Resume with: cyberos-ten holdco-flip resume --flip-id abc-123
$ cyberos-ten holdco-flip status --flip-id abc-123
Step: sg_entity_formed ✓
Step: acra_filings_prepared ✓
Step: shareholder_agreements_drafted ⟳ in progress
Step: esop_reissued ⏸ pending
Step: residency_migrated ⏸ pending
§4 — Acceptance criteria
- holdco_flip_step enum cardinality 8. 2. CEO+CFO+CLO triple-sign required. 3. Same-person across slots rejected. 4. CLI commands (init/resume/status). 5. 6-step orchestration. 6. Resumable from last completed. 7. UNIQUE(tenant_id) — one flip per tenant. 8. 4 memory audit kinds emitted. 9. PII scrubbed (output SHA256). 10. RLS denies cross-tenant. 11. Trace_id preserved. 12. Append-only via REVOKE except status cols. 13. Per-step checkpoint. 14. ACRA Form 24 generated. 15. ESOP re-issue under SG entity. 16. TASK-TEN-103 residency → sg-1 on completion. 17. Failed step → flip status=failed; resumable. 18. CLI exits non-zero on failure. 19. Wet-signature docs tracked out-of-band. 20. CLO role required for legal sign.
§5 — Verification
#[tokio::test]
async fn triple_sign_required() {
let ctx = TestContext::new_tenant().await;
let r = ctx.try_init_flip_with(ctx.ceo, ctx.cfo).await;
assert!(r.is_err()); // CLO missing
let r2 = ctx.init_flip_with(ctx.ceo, ctx.cfo, ctx.clo).await;
assert!(r2.is_ok());
}
#[tokio::test]
async fn resume_from_step() {
let ctx = TestContext::with_flip_at_step("acra_filings_prepared").await;
ctx.resume_flip(ctx.flip_id).await;
let steps = ctx.fetch_steps(ctx.flip_id).await;
let completed: Vec<_> = steps.iter().filter(|s| s.status == "completed").collect();
assert!(completed.len() >= 2); // sg_entity + acra
}
#[tokio::test]
async fn esop_reissue_under_sg() {
let ctx = TestContext::with_flip_at_esop_reissue().await;
ctx.run_step("esop_reissued").await;
let grants = ctx.fetch_grants(ctx.tenant_id).await;
assert!(grants.iter().all(|g| g.issuer_entity == "sg_holdco"));
}
// 5.4..5.10
§7 — Dependencies
Upstream: TASK-ESOP-001. Cross-module: TASK-ESOP-006 (acceleration triggers may overlap), TASK-TEN-103 (residency migration), TASK-AUTH-101 (CEO+CFO+CLO roles), TASK-MEMORY-111 (PII).
§10 — Failure modes
| Failure | Detection | Outcome | Recovery |
|---|---|---|---|
| One sig missing | gate | reject init | get sig |
| Same-person dual | validate | 403 | different signer |
| ACRA API down | client err | step=failed | retry |
| ACRA rejects filing | response | step=failed | fix + resubmit |
| ESOP re-issue partial | atomic per grant | partial; resume | retry |
| Residency migration mid-fail | rollback | sev-1 | manual fix |
| Wet-sig doc missing | track separately | warn + proceed | upload doc |
| Cross-tenant flip | RLS | 403 | inherent |
| Duplicate flip per tenant | UNIQUE | 409 | inherent |
| CLI interrupted | resume from checkpoint | inherent | inherent |
§11 — Implementation notes
- §11.1 BizFile API integration via Singapore gov OAuth; CTO obtains credentials.
- §11.2 ACRA Form 24 template version-pinned; updates via TASK-DOC-001 templates.
- §11.3 ESOP re-issue creates new TASK-ESOP-001 grants under SG entity with same vesting terms.
- §11.4 memory audit body: flip_id, step_name, status; output SHA256.
- §11.5 Triple-sign: CLI prompts for each signer's session token; backend verifies.
End of TASK-TEN-201 spec.