Task — engineering-spec@1

"TEN Singapore HoldCo flip CLI — `cyberos-ten holdco-flip` orchestrates ACRA filings + shareholder migration + ESOP transfer for VN → SG corporate restructure"

draftTASK-TEN-201
module ten · class product · priority p0 · created 2026-05-17 · shipped null
depends on TASK-ESOP-001 · blocks none

§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.

  1. MUST validate holdco_flip_step against closed enum per DEC-2401.
  1. MUST require CEO + CFO + CLO triple-sign at initiation per DEC-2403 via triple_sign_gate.rs::can_initiate(flip):
  1. MUST orchestrate 6 steps via CLI per DEC-2400:
  1. MUST be resumable per DEC-2402 — each step writes checkpoint; restart resumes from last completed.
  1. 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; ```

  1. 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> ``
  1. MUST emit 4 memory audit kinds per DEC-2404. PII per TASK-MEMORY-111: output_jsonb SHA256.
  1. MUST thread trace_id from CLI → orchestrator → step → audit.
  1. MUST NOT initiate without triple-sign per DEC-2403.
  1. MUST NOT mutate prior step per DEC-2402.
  1. 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

  1. 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

FailureDetectionOutcomeRecovery
One sig missinggatereject initget sig
Same-person dualvalidate403different signer
ACRA API downclient errstep=failedretry
ACRA rejects filingresponsestep=failedfix + resubmit
ESOP re-issue partialatomic per grantpartial; resumeretry
Residency migration mid-failrollbacksev-1manual fix
Wet-sig doc missingtrack separatelywarn + proceedupload doc
Cross-tenant flipRLS403inherent
Duplicate flip per tenantUNIQUE409inherent
CLI interruptedresume from checkpointinherentinherent

§11 — Implementation notes


End of TASK-TEN-201 spec.