Claim: partner identity is rooted in ops tree_nodes; Partner Profile OS is an adapter, not a peer ledger.
| Piece | Path |
|---|---|
| Schema | partner_profile_bindings in lib/operations/schema.ts |
| Bridge | lib/operations/partner-profile-bridge.ts |
| Templates | config/partner-templates/ (TOML, standalone — not Sports Terminal nested build) |
| Brands | TreeNodeId, PartnerTemplateId, PartnerProfileKey in lib/types/branded/operations.ts |
| Summary | buildOpsSummary().partners · portal panel Partner profiles |
| Sync | applyOpsSyncEvent(..., db) binds on account_assigned / telegram_linked |
| Backfill | bun scripts/backfill-partner-bindings.ts |
tree_nodes.id ──1:1──► partner_profile_bindings.tree_node_id
├── template_id → config/partner-templates/<id>.toml
├── profile_key → PartnerProfileKey (pp-…)
└── lifecycle_status ∈ signup | materialized | kyc_pending | active | suspended | terminated
Default template: default-prospect (DEFAULT_TEMPLATE_ID).
| Function | Role |
|---|---|
bindPartnerProfile / bindTemplate |
Create or refresh binding |
materializePartnerProfile / materializeProfile |
Join tree + platform accounts + provisioning |
queryPartnersSlice / getPartnersSummary |
Ops-summary counts |
templateIdForSource(source?) |
Onboard source → template (I1: always default) |
evaluateForNode |
Root-owned policy gate subset (I2 precursor) |
backfillPartnerBindings |
One-shot bind for active unbound nodes |
tree_nodes.status |
Default lifecycle on bind |
|---|---|
prospect |
materialized |
partner |
active |
| other active | materialized |
# Schema is applied via openOperationsDb → initSchema → migrateSchema
bun scripts/backfill-partner-bindings.ts --dry-run
bun scripts/backfill-partner-bindings.ts
# Summary includes partners slice
bun run ops:snapshot
# Tests
bun test tests/partner-profile-bridge.test.ts tests/ops-summary.test.ts
config/partner-templates/<slug>.toml with [meta] + [sor].templateIdForSource if source/referral should pick that slug.bindPartnerProfile(db, treeNodeId, { templateId: asPartnerTemplateId(slug) }).ops:snapshot and confirm portal Partner profiles panel.publishAndDispatch (play-dispatcher):
default-prospect).inferSignalTypeFromPlay → evaluateForNode (template SoR).recordGateDecision on every recipient.play.gate.denied outbox event.play.gate.adjusted event.play.gate.denied with reserve reason.Tests: tests/play-dispatcher-gate.test.ts · tests/toc-play-routing.test.ts.
After auto-bind, rankPlayRecipients orders agents by baked TOC weightedScore (readiness × expertWeight × limit freshness × Gate 12 — see lib/toc-ops/return-efficiency.ts).
When buffer.throttleOnboarding is true and an agent has a TOC-bound call sign with 0 < weightedScore < 0.5, dispatch defers before evaluateForNode:
recordGateDecision with action: 'defer'.play.gate.defer outbox (payload includes callSign, weightedScore, rankedRank).Override baked snapshot in tests via publishAndDispatch(..., { routingContext }).
| Event | Topic | When |
|---|---|---|
partner.bound |
identity |
First bindPartnerProfile insert via ops-sync (created === true) |
partner.welcome |
identity |
Telegram linked · HTML template pack + keyboard (r2 + telegram) |
partner.onboard.complete |
identity |
Package apply · onboard.complete.v1 (r2 + telegram) |
play.gate.denied / play.gate.adjusted / play.gate.defer |
plays |
Dispatch gate / TOC routing defer |
play.settled |
plays |
Settlement (profileKey attached when bound) |
Helpers: enqueueIdentityChannelEvent, enqueuePartnerWelcomeEvent, enqueueOnboardCompleteEvent, enqueuePlayGatedChannelEvent in lib/channels/outbox.ts.
Deep identity (cellphone → profile → seat → ChatChannelMeta → templates): partner-onboarding-package.md.
Process: processChannelOutbox (welcome/onboard.complete use telegram projector with parseMode: HTML; other identity events default R2; tests use deliver: false → local MemoryChannelStore).
templateIdForSource stub.partner-onboarding-package.md.agents/skills/ops-dual-mode-experiments/.agents/skills/partner-profile-os/ops-snapshot.md