Source document:
doc/StackFund_Feasibility_Report.mdThis document is aligned with feasibility report v3.1, "Core-B finalised architecture (FACE as a pure-narrative side-rail)". This update realises the v3.1 architecture change: FACE becomes a pure-narrative side-rail, the crowd layer never writes back to L4 — the two-key gate / bounded clamp / threshold-flip / TiltProvenance / zero-modifier CI are removed and replaced by an "L4 is not wired" structural firewall.
- StackFund's core is an autonomously-run Taiwan-ETF research micro-business; the product front-of-house is the "crowd-scenario rehearsal engine".
- Stripe handles only the business cash flow: subscriptions, operating spend, SaaS provisioning, spend refusal; it never places securities orders.
- Core-B's central contract is the FACE / ENGINE separation: the crowd-scenario engine is FACE; the deterministic Python trunk is ENGINE.
- The crowd-scenario engine emits only narrative, persona samples, a reaction chain and one non-authoritative
contrarian_modifier; it must not decide authoritative numbers like price / NAV / yield / weight / spend-cap, and never writes back to any decision layer. - The RebalancePlan is decided by the ScoreCard alone; the crowd modifier never enters the decision path, so every non-zero action is, by construction, independently justified by hard data.
- You can see v3 upgrade from the old five-layer architecture to the Core-B six-layer architecture.
- You can see that the L3 crowd-scenario engine is a pure-narrative side-rail leaf node that does not write back to L1 / L2 / L4 / L5 (zero wiring to the decision path).
- You can see that the Portfolio Manager consumes only the ScoreCard's output (hard-only plan / NO_ACTION) and never consumes the modifier.
- The Mermaid diagrams are layout-optimised only, keeping the original style — no colours or themes added.
| Analysis dimension | v3 reading | Architecture impact |
|---|---|---|
| Product front | The Pro plan sells a second-order reaction-chain narrative, not stronger numbers | Adds the Crowd Scenario FACE |
| Trunk credibility | All market numbers, weights and spend are computed by deterministic Python | L1 / L2 / L4 / L5 stay authoritative |
| Firewall | L3 only emits typed objects, imports no compute or Stripe modules, and is not wired to the decision path | Needs five layers of control: type, import, input, flag, wiring |
| How the PM consumes | L4 consumes no modifier at all — only the ScoreCard | L4 does not import / does not receive ContrarianSignal (wiring CI) |
| Regulatory risk | The firewall is a correctness control, not the primary advisory defence | The compliance line is still: non-individualised, test-mode, zero orders, disclaimer |
| Demo strategy | The crowd engine takes the WOW beat, but earn / spend / refused-spend stay the spine | The runbook marks live / replayed; live re-bake is forbidden |
This diagram keeps only the high-level system boundaries; detailed object relationships are left to the later diagrams, so the overview doesn't become a circuit diagram.
flowchart TB
subgraph Inputs["Inputs & governance"]
direction LR
Subscriber["Subscriber"]
MarketSources["Market data sources<br/>TWSE / TPEX / MOPS / Yahoo / News"]
StripeLayer["Stripe<br/>Billing / Skills / spend cap"]
Sandbox["NemoClaw / OpenShell<br/>sandbox policy"]
end
subgraph Runtime["StackFund Runtime"]
direction TB
Boundary["Controlled-agent execution boundary<br/>Hermes / Crowd / FinOps"]
Engine["ENGINE: deterministic trunk<br/>ETF Data Book -> Scorecard -> Portfolio -> FinOps -> Audit/P&L"]
Face["FACE: non-authoritative front<br/>ScenarioSeed -> Crowd Scenario -> ContrarianSignal"]
Firewall["Core-B firewall<br/>narrative only / no authoritative numbers / no write-back to engine"]
Boundary --> Engine
Engine -->|"frozen projection"| Face
Face --> Firewall
end
subgraph Outputs["Product & evidence"]
direction LR
Watch["Watch free number card"]
Pro["Pro crowd-scenario report"]
Desk["Desk roadmap"]
PNL["Operational P&L<br/>before / after evidence"]
Watch --> Pro -.-> Desk
Pro --> PNL
end
Subscriber --> StripeLayer
MarketSources --> Boundary
StripeLayer --> Boundary
Sandbox --> Boundary
Engine --> Watch
Engine --> Pro
Engine --> PNL
Watchsells deterministic facts;Prosells the crowd-scenario narrative plus the underlying research conclusion.- The crowd-scenario engine is a pure-narrative leaf node: it reads
ScenarioSeedand emits aContrarianSignalthat flows only to the Pro report and the audit — it cannot write back to L1 / L2 / L4 / L5. - L4 is the steering wheel: it consumes only the ScoreCard to produce a hard-only plan or NO_ACTION, and accepts no tilt.
flowchart LR
subgraph EngineMain["ENGINE trunk"]
direction LR
L1["L1 ETF Data Book<br/>deterministic ingest<br/>price / NAV / discount-premium / tracking error / yield / freshness"]
L2["L2 Deterministic Scorecard<br/>trend / fundamental / valuation / catalyst / risk"]
HardPlan["Hard-only RebalancePlan"]
L4["L4 Portfolio Manager<br/>ScoreCard only<br/>hard-only plan / NO_ACTION"]
L5["L5 FinOps & Execution<br/>Stripe earn / spend<br/>Value-of-Information gate"]
L6["L6 Audit & Operational P&L<br/>Full provenance / replay / P&L"]
L1 --> L2 --> HardPlan --> L4 --> L5 --> L6
end
subgraph FaceLane["FACE side-rail (pure narrative)"]
direction LR
Seed["ScenarioSeed<br/>read-only, frozen, ordinal buckets"]
L3["L3 Crowd Scenario Engine<br/>N=20-50 synthetic personas<br/>LLM writes text only"]
Signal["ContrarianSignal<br/>narrative annotation [-1,+1]<br/>non-authoritative"]
Seed -.-> L3 -.-> Signal
end
ProReport["Pro report<br/>narrative = product"]
L1 -.->|"projection only"| Seed
Signal -.->|"narrative only, NO write-back"| ProReport
Signal -.->|"record only"| L6
| Layer | Authority | Responsibility | Must not |
|---|---|---|---|
| L1 ETF Data Book | Authoritative | Fetch, normalise, mark freshness, produce DataBook / ScenarioSeed | Draw investment conclusions |
| L2 Deterministic Scorecard | Authoritative | Produce the scorecard and hard signals via transparent formulas | Let the LLM do mental arithmetic |
| L3 Crowd Scenario Engine | Non-authoritative | Produce the second-order reaction-chain narrative, persona samples, and a non-authoritative modifier annotation | Decide price, weight, spend or orders, or write back to any decision layer |
| L4 Portfolio Manager | Authoritative | Consume only the ScoreCard to produce a hard-only plan, NO_ACTION, decision provenance | Import or receive ContrarianSignal |
| L5 FinOps & Execution | Authoritative | Stripe earn/spend, refused spend, VoI gate | Be primarily triggered by a crowd signal |
| L6 Audit & Operational P&L | Authoritative | Provenance, replay, Operational P&L, disclaimer | Store sensitive tokens |
sequenceDiagram
autonumber
actor User as Subscriber
participant Billing as Stripe Billing
participant Hermes as Hermes Agent
participant Shell as OpenShell
participant L1 as ETF Data Book
participant L2 as Scorecard
participant L3 as Crowd Scenario FACE
participant L4 as Portfolio Manager
participant L5 as FinOps
participant Stripe as Stripe Skills
participant L6 as Audit & P&L
User->>Billing: Watch / Pro subscription or test-mode payment
Billing-->>L6: revenue receipt
Hermes->>Shell: start controlled workflow
Shell->>L1: allow data fetch
L1->>L2: DataBook + hard metrics
L1-->>L3: frozen ScenarioSeed only
L2-->>L4: hard signals + hard-only plan
alt Pro report and VoI allows running the scenario
L3->>L3: seeded personas + LLM reaction text
L3-->>L6: CrowdScenarioReport + ContrarianSignal (record only, non-authoritative)
else low-value event or demo fallback
L3->>L3: replay / skipped (no LLM)
end
Note over L3,L4: L4 does not import / does not receive ContrarianSignal (wiring firewall)
L4->>L4: build plan from ScoreCard only (no modifier)
alt hard-only plan crosses the threshold
L4-->>L6: RebalancePlan + DecisionProvenance
else within tolerance
L4-->>L6: NO_ACTION + hard-only reason
end
L5->>L5: deterministic Value-of-Information gate
L5->>Stripe: provision / upgrade / downgrade
alt within cap and allowlist
Stripe-->>L5: spend receipt
else over budget or needs manual approval
Stripe-->>L5: refused spend
end
L5-->>L6: OperationalReceipt
flowchart LR
Seed["ScenarioSeed<br/>frozen ordinal context<br/>no raw price setters"]
subgraph InputChecks["Input / import checks"]
direction TB
InputGuard["Input guard<br/>ScenarioSeed read-only"]
ImportGuard["Import guard<br/>no L1/L2/L4/L5 compute imports"]
end
subgraph CrowdLayer["L3 Crowd Scenario Engine"]
direction LR
Personas["Synthetic personas<br/>N=20-50"]
Reaction["LLM reaction text<br/>stance token only"]
Aggregate["Deterministic aggregate<br/>closed-vocab stance"]
Personas --> Reaction --> Aggregate
end
subgraph OutputChecks["Output checks"]
direction TB
TypeGuard["Type guard<br/>no price / NAV / yield / weight / cap fields"]
FlagGuard["Flag guard<br/>assert is_authoritative=false"]
WireGuard["Wiring guard<br/>L4 import graph has no L3/Signal"]
Signal["ContrarianSignal<br/>modifier in [-1,+1]<br/>is_authoritative=false"]
TypeGuard --> FlagGuard --> WireGuard --> Signal
end
ProReport["Pro report<br/>narrative = product"]
L4Decision["L4 decision path<br/>ScoreCard only (no Signal)"]
Audit["L6 Audit<br/>record only"]
Reject["Schema violation<br/>abort / strip / fail closed"]
Seed --> InputGuard --> Personas
ImportGuard -.-> Personas
Aggregate --> TypeGuard
TypeGuard -->|"forbidden numeric/action field"| Reject
FlagGuard -->|"flag not false"| Reject
Signal -.->|"narrative only, NO write-back"| ProReport
Signal -->|"record only"| Audit
WireGuard --x L4Decision
| Invariant | How it is checked |
|---|---|
| L3 emits no authoritative market or action fields | CrowdScenarioReport / ContrarianSignal schema, fail-closed |
| L3 imports no compute, Portfolio or Stripe modules | test_firewall_no_imports / import-graph grep |
L3 reads only the frozen ScenarioSeed |
No setters; inputs are already bucketed |
is_authoritative is always false |
Schema const + runtime assert |
| Persona text must not carry into a numeric decision | Forbidden-output scan / numeric-token strip |
The L4 decision path does not receive ContrarianSignal |
L4 import-graph grep; build_rebalance_plan signature takes only ScoreCard |
flowchart TD
Start["Receive ScoreCard<br/>(no ContrarianSignal parameter)"]
HardOnly["build_plan_from_scorecard<br/>sole input: ScoreCard"]
HardEmpty{"hard_plan empty<br/>or within tolerance?"}
NoAction["NO_ACTION<br/>with hard-only reason"]
Final["RebalancePlan<br/>final_delta = hard_delta<br/>with two independent hard reasons"]
Provenance["DecisionProvenance<br/>scorecard_id / hard_delta / hard_signals"]
Start --> HardOnly --> HardEmpty
HardEmpty -->|"yes"| NoAction
HardEmpty -->|"no"| Final --> Provenance
- The crowd modifier never enters L4; the RebalancePlan is decided by the ScoreCard alone and is independently justifiable by construction.
- L4 simply never receives the modifier, so there is no possibility of "L3 quietly becoming the deciding factor"; both NO_ACTION and actions are pure hard data.
- The wiring CI is the machine-checkable core: it asserts that the L4 import graph contains no L3 / ContrarianSignal, and that the
build_rebalance_plansignature takes only the ScoreCard.
The v3 schema set grows from three to four: ETFResearchReport, RebalancePlan, OperationalReceipt, CrowdScenarioReport. It also adds ScenarioSeed, ContrarianSignal, DecisionProvenance as the key objects for the firewall and auditability. The crowd-layer output (ContrarianSignal) is wired only to the report and the audit, never to the RebalancePlan.
erDiagram
ETF ||--o{ DATA_BOOK : has
DATA_BOOK ||--|| SCORECARD : produces
DATA_BOOK ||--o| SCENARIO_SEED : projects
SCENARIO_SEED ||--o| CROWD_SCENARIO_REPORT : rehearses
CROWD_SCENARIO_REPORT ||--|| CONTRARIAN_SIGNAL : emits
SCORECARD ||--o| REBALANCE_PLAN : justifies
REBALANCE_PLAN ||--o| DECISION_PROVENANCE : records
SUBSCRIPTION ||--o{ OPERATIONAL_RECEIPT : revenue
TOOL_SERVICE ||--o{ OPERATIONAL_RECEIPT : cost
OPERATIONAL_RECEIPT }o--|| OPERATIONAL_PNL : rolls_up
REBALANCE_PLAN }o--|| OPERATIONAL_PNL : evidence
ETF {
string symbol PK
string name
string market
}
DATA_BOOK {
string id PK
string etf_symbol FK
datetime observed_at
string freshness
string book_hash
}
SCORECARD {
string id PK
string databook_id FK
float trend_score
float valuation_score
float yield_score
float risk_score
}
SCENARIO_SEED {
string id PK
string databook_id FK
string event_label
string seed_hash
string ordinal_context
}
CROWD_SCENARIO_REPORT {
string id PK
string scenario_seed_id FK
boolean is_authoritative
string reaction_chain
string persona_samples
string disclaimer
}
CONTRARIAN_SIGNAL {
string id PK
string report_id FK
float contrarian_modifier
boolean is_authoritative
string formula_id
}
REBALANCE_PLAN {
string id PK
string scorecard_id FK
string action
float hard_delta_pp
string no_action_reason
}
DECISION_PROVENANCE {
string id PK
string decision_id FK
string scorecard_id
float hard_delta_pp
string hard_signals
string action
}
SUBSCRIPTION {
string id PK
string stripe_customer_id
string plan
string status
}
TOOL_SERVICE {
string id PK
string vendor
string category
string tier
}
OPERATIONAL_RECEIPT {
string id PK
string type
string status
float amount
string reason
}
OPERATIONAL_PNL {
string id PK
float revenue
float cost
float gross_margin
datetime period
}
CrowdScenarioReport's root-levelis_authoritativemust befalse, withadditionalProperties:false.ContrarianSignal'scontrarian_modifier ∈ [-1,+1]is a non-authoritative report annotation; it enters no decision path.RebalancePlanis produced by the ScoreCard alone, preserving hard-only reasons and theNO_ACTIONreason (no tilt provenance).OperationalReceiptmust be able to representspend,earn,refused_spend,blocked,manual_review.- No schema may store sensitive tokens, plaintext keys or payment credentials.
flowchart TB
DataBook["databook.json"]
SeedBuilder["seed_builder.py<br/>Python only<br/>DataBook -> EventSeed"]
SeedLock["seed.lock.json<br/>rng_seed / roster_hash / model_id / temp=0"]
PersonaSim["persona_sim.py<br/>seeded roster + sparse edges"]
Round1["Round 1<br/>persona independent reaction"]
Round2["Round 2<br/>persona reads K neighbors"]
Stance["closed-vocab stance token"]
CrossCheck["stance re-derive check<br/>fallback if drift"]
Aggregate["aggregate.py<br/>deterministic modifier + reaction_chain"]
Narrative["final LLM narrative<br/>only from computed values"]
Report["CrowdScenarioReport<br/>schema + disclaimer"]
DryRun["--dry-run stub<br/>zero LLM / zero spend"]
DataBook --> SeedBuilder --> SeedLock --> PersonaSim
PersonaSim --> Round1 --> Round2 --> Stance --> CrossCheck --> Aggregate
Aggregate --> Narrative --> Report
DryRun -.-> PersonaSim
| Reading | Architecture handling |
|---|---|
| Do not integrate MiroFish directly | AGPL, a long-running service, OASIS/Zep, high LLM cost — all unsuitable for an MVP |
| Use clean-room distillation | Take only the "heterogeneous personas form a second-order chain" idea; copy no code / prompt / schema |
| The modifier claims no greater accuracy | The value is pinned to the reaction_chain narrative, not to a scalar |
| Reproducible ≠ validated | seed.lock.json makes the replay reproducible, but the demo must state it is not backtested |
| The front is degradable | If behind schedule by 6/29, cut the replay animation for a static card; the core is untouched |
flowchart LR
subgraph Plans["Stripe subscription plans"]
direction TB
WatchPlan["Watch NT$0<br/>deterministic number card"]
ProPlan["Pro ~NT$299/mo<br/>crowd scenario narrative"]
DeskPlan["Desk ~NT$999/mo<br/>roadmap"]
end
subgraph Earn["EARN"]
direction TB
Billing["Stripe Billing"]
Revenue["Revenue Receipt"]
Billing --> Revenue
end
subgraph Spend["SPEND"]
direction TB
VoI["Value-of-Information gate<br/>materiality + cap headroom"]
Provision["SaaS provision / upgrade / downgrade"]
Refused["Refused Spend<br/>cap breach / not worth it"]
VoI -->|"worth it"| Provision
VoI -->|"not worth it"| Refused
end
subgraph PNL["Operational P&L"]
direction TB
Cost["Cost Receipt"]
Margin["Revenue - Cost"]
Evidence["before / after evidence"]
Margin --> Evidence
end
WatchPlan --> Billing
ProPlan --> Billing
DeskPlan -.-> Billing
Revenue --> Margin
Provision --> Cost --> Margin
Refused --> Evidence
contrarian_modifierplays no part whatsoever in a spend decision (not even as a tie-breaker); spend is gated purely by the deterministic VoI gate.- Whether a paid report is worth running is judged by the deterministic VoI gate.
- Refused spend is a core demo beat: the agent doesn't just spend money — it can also refuse to spend when it isn't worth it.
flowchart TD
subgraph Core["Days 1-6: freeze the core first"]
direction TB
D1["Day 1<br/>Stripe TW dual-flow + TWSE/Yahoo reachable"]
D2["Day 2<br/>lock the four schemas + freeze fixtures"]
D3["Day 3<br/>scorecard + cost optimizer + unit tests"]
D4["Day 4<br/>Stripe SPEND + REFUSED SPEND"]
D5["Day 5<br/>Stripe EARN + P&L"]
D6["Day 6<br/>firewall + hard-only PM + wiring CI<br/>CORE FROZEN"]
D1 --> D2 --> D3 --> D4 --> D5 --> D6
end
subgraph FaceBuild["Days 7-9: the front is degradable"]
direction TB
D7["Day 7<br/>crowd scenario scaffold"]
D8["Day 8<br/>bake LLM persona text + replay artifact"]
D9["Day 9<br/>animation + non-authoritative badge + hard cut"]
D7 --> D8 --> D9
end
subgraph Delivery["Days 10-12: delivery"]
direction TB
D10["Day 10<br/>NemoClaw wrap + rehearsal"]
D11["Day 11<br/>110s fallback recording + drills"]
D12["Day 12<br/>buffer / submit"]
D10 --> D11 --> D12
end
Core --> FaceBuild --> Delivery
| Segment | Purpose | Architecture signal |
|---|---|---|
| 0:00-0:10 | Firewall cold open | The crowd engine is non-authoritative, touches no amount/weight/order |
| 0:10-0:24 | EARN live | Stripe Billing revenue flows into the P&L |
| 0:24-0:38 | SPEND pre-staged | The agent provisions a tool; the cost flows into the P&L |
| 0:38-0:50 | REFUSED SPEND live error path | Stripe cap hard-refuses; the agent does NO_ACTION |
| 0:50-0:78 | Scenario engine WOW replay | is_authoritative=false; auditable but not validated |
| 0:78-0:96 | Hard cut back to the engine | Show the import graph: L4 never received the crowd layer (it was never wired in) |
| 0:96-0:110 | Viability close | Before/after P&L and the disclaimer |
flowchart LR
subgraph Risks["Risks"]
direction TB
R1["R1 Stripe TW earn / spend eligibility"]
R2["R2 SITA investment-advisory regulation"]
R2B["R2b Market integrity / appearance of information-based manipulation"]
R2C["R2c Headlining weakens the research carve-out"]
R3["R3 Live demo execution failure"]
R4["R4 Data-source stability"]
R5["R5 Reproducible is not validated"]
R6["R6 Agency perception"]
R7["R7 Clean-room / licensing credibility"]
end
subgraph Mitigations["Mitigations"]
direction TB
M1["dry_run / free_only / live_limited"]
M2["Non-individualised publication framing<br/>test-mode / zero orders / no buy-sell instructions"]
M2B["Hypothetical stress scenario<br/>no directional claim about a named ETF's price"]
M2C["Scenario-rehearsal / stress-test wording"]
M3["pre-stage + replay + fallback recording"]
M4["official sources first + freshness + fixture fallback"]
M5["proactively disclose not-backtested<br/>modifier value pinned to narrative"]
M6["show provision / cap breach / downgrade"]
M7["honest clean-room.md<br/>copy no code/prompt/schema"]
end
R1 --> M1
R2 --> M2
R2B --> M2B
R2C --> M2C
R3 --> M3
R4 --> M4
R5 --> M5
R6 --> M6
R7 --> M7
| Risk | Updated architecture response |
|---|---|
| Firewall write-back risk | FACE is a pure-narrative side-rail; the modifier never enters L4; wiring CI (L4 import graph has no L3 / Signal) |
| Modifier influencing spend | Spend is gated purely by the VoI gate; the modifier plays no part (not even as a tie-breaker) |
| The three demo Stripe calls failing | live/replayed marking, pre-staged spend, fallback recording |
| Headline narrative misread as a forecast | Use "scenario rehearsal / stress test"; forbid "forecast / prediction" |
| Regulatory exposure | The primary defence is non-individualised, test-mode, zero orders, disclaimer — not the firewall |
| Clean-room credibility | Honestly acknowledge having understood the source, but copied no code / prompt / schema |
| Decision | Why it matters |
|---|---|
| Promote the crowd engine to the product front, but keep it non-authoritative | Adds differentiation while avoiding polluting the core decision |
| The Core-B six-layer architecture | Makes the FACE / ENGINE boundary explicit |
ContrarianSignal typed output |
Lets L3 emit only a bounded, auditable, non-authoritative object, wired only to the report and audit — never into L4 |
| Hard-only only | L4 consumes only the ScoreCard; the crowd narrative never enters a decision |
| The wiring firewall | L4 does not import / does not receive ContrarianSignal — structurally zero-wired |
| The wiring CI | Turns "L4 never wired in the crowd layer" into a testable invariant (import graph) |
| The VoI gate controlling paid reports | Keeps the spend decision governed by hard data and budget |
The updated StackFund architecture is no longer just "a Taiwan-ETF research desk + Stripe cash flow". v3 makes the product front-of-house a "crowd-scenario rehearsal engine", but the most important architectural value is that it is strictly isolated: the crowd layer owns the demonstrable, sellable, replayable second-order narrative; the deterministic trunk owns all numbers, weights, spend and P&L.
Core-B holds on three checkable commitments:
- The crowd layer emits only a
ContrarianSignal, withis_authoritative=false. - Every RebalancePlan in L4 is decided by the ScoreCard alone; the crowd layer never enters the decision path (provable by the wiring CI).
- L5's spend is governed by the deterministic VoI gate and a hard Stripe cap; the crowd layer cannot primarily trigger a spend.
So the conclusion of this architecture analysis is: v3 is more presentation-advantaged than the old version, but also more dependent on the firewall, the wording, demo discipline and proactive disclosure. As long as the Core is frozen before Day 6 (firewall, hard-only PM, P&L, wiring CI), even if the scenario animation is cut at the end, StackFund still retains the core persuasiveness of earn / spend / run real operations.