feat(docs): PR 8 final polish + manifest + flag + backlinks per execute-plan
This commit is contained in:
parent
826d21e020
commit
3590b8de5f
35 changed files with 394 additions and 150 deletions
|
|
@ -178,3 +178,4 @@ This brief stays a **roster + lifecycle** doc — what specialists exist (L1), h
|
|||
- [brief J](./_engineering-v2-port-map.md) — port verdicts that determine which specialists exist + their default posture.
|
||||
- [brief O](./O-surfaces-roster.brief.md) — canonical surfaces roster that drives the hybrid specialist-count decision in this brief.
|
||||
- [`voice.brief.md`](./00-system-voice.md) §V4 — per-specialist voice modulation.
|
||||
- placement-market (PR 7): adds `specialist-placement-router` (consumes L's prospect-resolver + graph/N/Y); T strategist panels for placement funnels + full integration/tests. Backlink + cross-ref per execute-plan PR7 in placement-market.brief.md + ai-copilot docs.
|
||||
|
|
|
|||
|
|
@ -253,3 +253,4 @@ The coop's existential risk is misuse — competitive suppression, personal vend
|
|||
- [Brief M](./M-error-degraded-modes.brief.md) §M3 — publish confirmation uses the high-stakes interrupt pattern.
|
||||
- [`voice.brief.md`](./00-system-voice.md) §V2c — coop UI uses plain register; metaphor would be inappropriate for safety-intel.
|
||||
- [[jobs-to-metaphor]] memory — coop intel is the rare CocotteAI surface where the cooking metaphor is OFF entirely.
|
||||
- placement-market (PR 7): coop revocation (attestation drop) immediately cancels active placement offers for that peer (router + placements.service); in-flight handoffs human-flagged + audited. Hard gates in N + placement router per brief States/Constraints/Observability + contract Never/Failure. Revocation propagation + full tests. See placement-market.brief.md, contract.md. Backlinks/cites per execute-plan PR7 + N cross-ref.
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ Resolutions (with date) get the original line struck through; do not delete —
|
|||
- **Z-Q2** Cost transparency default — surface in chat / surface in S / both? `[blocking]` (lean: opt-in panel + warning on expensive turns).
|
||||
- **Z-Q4** Billing root location — own root or under Settings → Account? `[nice-to-have]` (lean: own root, paired with Deposits).
|
||||
- **Z-Q5** Trial period for tier upgrades — universal or only specific high-value gates? `[exploratory]` (lean: only specialist-gates with proven discovery friction).
|
||||
- ~~**Z-Q6** Placement revenue share settlement integration with Z (cross-provider ledger vs per-provider)? → resolved per execute-plan PR 7 + placement-market.brief.md: per-provider append-only `placement_ledger` with `billing_ref` link + policy snapshot; Z settlement reads placement_ledger entries for platform share / provider payout attribution. Cross-provider via N-coop graph later (see N brief, prospecting graph). Full e2e + failure modes in placements tests.~~
|
||||
|
||||
### AA — marketing site (cocottetech.com)
|
||||
- **AA-Q1** Dark mode default for marketing? `[exploratory]` (lean: respect `prefers-color-scheme`, light fallback, no toggle in P0).
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ The dashboard is the **strategist's front-window**, not a separate specialist. I
|
|||
- **Voice lean**: working register throughout — analytical, literary; hearth on insight chips when the news is good; plain when revenue dips or a cohort hard-flatlines.
|
||||
- **Pair-with**: [strategist contract](./specialist-strategist.contract.md), [`day-in-life.flow.md`](./day-in-life.flow.md) (dashboard appears in the day flow when Quinn checks in on web companion).
|
||||
- **Blocking Qs**: see [OPEN-DECISIONS.md](./OPEN-DECISIONS.md) → T-Q1 cadence-of-refresh, T-Q2 web-vs-iOS depth parity.
|
||||
- Placement-market funnels (PR 7 integration): T2 prospect funnel + per-surface panels scoped to placement via surface_metrics/agent_actions (placement_match_proposed etc). North-star instrumentation, anomaly detection per-provider (T), strategist panels. See placement-market.brief.md (PR 7 + Observability), contract (Outputs, failure), cross-provider-graph for outcome contribution, N for revocation, Z for settlement ledger. Backlinks + cites added per execute-plan.
|
||||
|
||||
## States to design
|
||||
|
||||
|
|
|
|||
|
|
@ -212,3 +212,4 @@ When a provider leaves the platform, the offboard flow has to reconcile **person
|
|||
- [Brief Z](./Z-billing-payments.brief.md) — provider pricing + billing, signposted from Y but out-of-scope here.
|
||||
- [Brief AA](./AA-marketing-site.brief.md) — `cocottetech.com` public surface; Y1a entry.
|
||||
- [Brief AC](./AC-second-member-onboarding.brief.md) — distinct from Y; new staff member joining an existing provider's org.
|
||||
- placement-market (PR 7 cross-feature): Y vetting gates + org overlay referenced by placement router (distinct from Y asset-share "not a marketplace"); PR7 updates + full backlinks in Y/N/L/T/Z/prospecting/client-area/DESIGN + placement docs. See placement-market.brief.md.
|
||||
|
|
|
|||
|
|
@ -273,3 +273,4 @@ Never:
|
|||
- [brief I](./I-audit-trust-replay.brief.md) — deposit-match attribution flows through the trust loop.
|
||||
- [brief U](./U-global-search.brief.md) — receipts + deposits searchable as types.
|
||||
- DESIGN §5 — person-first tenancy with optional org_id; billing records inherit.
|
||||
- [placement-market.brief.md](../placement-market/placement-market.brief.md) §PR 7 + contract — end-to-end revenue share settlement via placement_ledger (billing_ref) + policy snapshot at offer time; integrates Z settlement (per-provider; cross later via N). North-star + anomaly via T. See also billing-overview.screen.md. PR7: full tests + coop revocation + graph outcome feed.
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ Billing + payments overview for Quinn's CocotteAI subscription + usage costs. Im
|
|||
│ ─── Cost so far ─── │
|
||||
│ Base $49.00 │
|
||||
│ Overage (GPU) $0.00 │
|
||||
│ Placement shares +$12.50 (Z settlement) │
|
||||
│ ──────── │
|
||||
│ Total $49.00 │
|
||||
│ [ See itemized → ] │
|
||||
|
|
@ -84,6 +85,7 @@ Billing + payments overview for Quinn's CocotteAI subscription + usage costs. Im
|
|||
- [Brief M](./M-error-degraded-modes.brief.md) — payment-failure degradation.
|
||||
- [Brief I](./I-audit-trust-replay.brief.md) — every billing change audited.
|
||||
- [Brief V](./V-data-portability-erasure.brief.md) — cancel-plan flows into account-close.
|
||||
- [placement-market.brief.md](../placement-market/placement-market.brief.md) + contract (PR 7) — placement_ledger feeds Z for revenue share settlement (end-to-end + per-provider anomaly; north-star instrumentation; cites T, N coop revocation). See settlement ledger in placement-market.screen.md. Backlink per execute-plan PR 7.
|
||||
|
||||
## Out of scope
|
||||
- Tier plan comparison details (separate marketing-side page).
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ The reader of patterns. Speaks in plans, not actions.
|
|||
|
||||
Reads recent `engagement_events`, `content_posts` performance, `agent_actions` history, prospect funnel data, **`metric_aggregates` (per-user cross-surface rollups)**, **`prospect_touchpoints` (cross-surface attribution chains)**, and **per-surface `surface_metrics`** (per [_engineering-surface-metrics.md](./_engineering-surface-metrics.md)). Produces weekly content plans, tour-timing windows, prospect-follow-up clustering recommendations, cohort warmth reads, OF-X funnel reads, **cross-surface attribution insights** ("Tryst → iMessage → OF is your warmest path; the Tryst-bio nudge could amplify it"), **tier-upgrade ROI estimates** (when Quinn is on a tier without native analytics on a surface, compute estimated visibility if upgraded). Answers ad-hoc analytical questions Quinn asks ("what's the OF cohort looking like?", "what did Tryst contribute this month?", "which model attributes Tryst lowest?").
|
||||
|
||||
Placement funnel metrics (via surface_metrics placement_* kinds + agent_actions per placement-market PR 3) feed strategist for discovery→offer→confirm→handoff panels (T; see placement-market.contract.md Outputs + brief PR 7). Added in PR 3 to metrics surface.
|
||||
Placement funnel metrics (via surface_metrics placement_* kinds + agent_actions per placement-market PR 3) feed strategist for discovery→offer→confirm→handoff panels (T; see placement-market.contract.md Outputs + brief PR 7). Added in PR 3 to metrics surface. PR 7: full integration + strategist T panels scoped, north-star, per-provider anomaly, end-to-end tests exercised; graph outcome contrib + N revocation + Z billing settlement backlinks. See placement-market.brief.md PR7.
|
||||
|
||||
## Auto
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ One React SPA, served at each provider's brand domain. Three tiers of surface:
|
|||
- **Not admin.** The provider's own management interface (content authoring, document creation, client CRM, analytics) lives in `provider-portal`. Client Area is read-only from the client's perspective.
|
||||
- **Not AI copilot.** `ai-copilot` is a provider-facing iOS + web surface. Clients have no AI interaction.
|
||||
- **Not messaging.** Inbound messages from clients (iMessage, SMS, email) flow through `mac-sync` → `messenger` → `engagement-ingestor`. Client Area surfaces only static documents; it has no real-time chat.
|
||||
- **Not a marketplace.** No browsing, no discovery, no public-facing listings. Clients reach the Client Area through a direct link from the provider (SMS, email, iMessage).
|
||||
- **Not a marketplace.** No browsing, no discovery, no public-facing listings. Clients reach the Client Area through a direct link from the provider (SMS, email, iMessage). (Contrast: client-facing placement discovery in placement-market.brief.md — distinct per key decisions; PR 7 backlink.)
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -58,5 +58,6 @@ All design docs now live under `.project/designs/<feature>/`. These placement-ma
|
|||
2. **Level-0 (once gated):** core placement entities + migrations + basic CRUD + specialist skeleton in platform-api + ai-core contrib upstream.
|
||||
3. **Subsequent:** discovery matching (graph + surface metrics), offer routing + MCP, policy/ledger surfaces, web-fe secondary, integration tests, strategist metrics, docs backlinks.
|
||||
4. **v2+:** full network-scale conversion measurement against north-star (filled slots + reclaimed free-time).
|
||||
**PR 7 status:** Integration with graph/coop/billing + strategist metrics + full tests complete (cross updates, vitest+e2e, OPEN-DECISIONS, all backlinks + cites per task). See placement-market.brief.md PR Plan.
|
||||
|
||||
Status declared everywhere: **P5+ / v2 — parked / future.**
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ Secondary web: `/market` or `/placements` browser (listings, offer inbox, policy
|
|||
- `@platform/infrastructure/sql/migrations/0001_tenancy_and_content.sql` (tenancy pattern, agent_actions, engagement_events, surface_kind), `0005_surface_metrics.sql`, `0006_peer_social.sql` (coop patterns)
|
||||
- `client-area/` (direct-link contrast)
|
||||
- `.archive/ARCHIVED.md` (v1 marketplace mining target at platform.1/features/marketplace/)
|
||||
- PR 7 executed: cross-feature (prospecting graph outcome contrib, N coop revocation gates/propagation in router, Z billing settlement on ledger, T-analytics placement funnel panels via surface_metrics/agent_actions); vitest + e2e in placements + specialist logic; OPEN-DECISIONS updated; backlinks + JSDoc cites in all related (Y/N/L/T/Z, prospecting, client-area, DESIGN, placement docs, briefs/contracts). See also placement-market.contract.md, screen.md, README.md. Per execute-plan PR 7 + brief.
|
||||
|
||||
## PR Plan
|
||||
|
||||
|
|
|
|||
|
|
@ -192,3 +192,4 @@ Entity shapes (TypeORM) mirror agent-action.entity.ts ( @Column user_id, @Column
|
|||
- `@platform/infrastructure/sql/migrations/0001_tenancy_and_content.sql` (agent_actions, engagement_events, surface_kind), `0005_surface_metrics.sql`, `0006_peer_social.sql`
|
||||
- `@platform/codebase/@packages/surface-adapter-contracts/src/` (SurfaceKind reuse)
|
||||
- `@platform/codebase/@features/platform-api/src/entities/agent-action.entity.ts` (audit pattern)
|
||||
- placement-market.brief.md PR 7 (graph/coop/billing + strategist T + full tests); updates to prospecting graph (outcome), N (revocation), Z (ledger settlement), T (placement funnel panels via surface_metrics/agent_actions). Vitest/e2e + OPEN-DECISIONS + backlinks all docs. Cites added.
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@
|
|||
- `../prospecting/internal-marketplace.screen.md` (parallel layout + states + privacy for internal routing)
|
||||
- `../ai-copilot/approval-card.screen.md` (primary card consumption; this is rich-output supplement)
|
||||
- `../ai-copilot/billing-overview.screen.md` (settlement integration)
|
||||
- placement-market.brief.md (PR 7: Z ledger settlement, T panels, N coop, graph outcome; full tests; backlinks everywhere)
|
||||
- `../ai-copilot/endorse-peer.screen.md`, `../ai-copilot/N-provider-coop.brief.md` (vetting entry points)
|
||||
- `../ai-copilot/I-audit-trust-replay.brief.md` (replay affordances)
|
||||
- `../ai-copilot/Y-cross-org-marketplace.brief.md` (external marketplace; orthogonal)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ Pooled signal returned per subject (all consented, all auditable):
|
|||
- **Qualification fingerprint** — feature vector summarizing how the subject communicates (response latency, message-length distribution, vocabulary markers), without leaking message content.
|
||||
- **Safety flags** — `N-provider-coop` peer-attested reports re-surfaced on the subject (existing primitive).
|
||||
- **Funnel velocity hints** — typical touchpoint-count and elapsed time from first message to outcome, anonymised across contributing providers.
|
||||
- **Placement outcome contribution** (PR 7) — booked/declined/ghost via client-facing placement handoff (from placement-market) feeds the aggregates for cross-provider discovery. See placement-market.brief.md (PR7 + graph feeds), contract (Outputs: "To graph: outcome contribution"), placement_ledger + handoff for consented. Updates per execute-plan PR 7.
|
||||
|
||||
## States
|
||||
|
||||
|
|
@ -43,3 +44,4 @@ Pooled signal returned per subject (all consented, all auditable):
|
|||
- `../ai-copilot/N-provider-coop.brief.md` (safety-flag primitive)
|
||||
- `../ai-copilot/Y-cross-org-marketplace.brief.md §Y4` (no-auto-merge rule)
|
||||
- `../ai-copilot/_engineering-surface-metrics.md` (touchpoint schema + anonymous invariant)
|
||||
- `../placement-market/placement-market.brief.md` PR 7 + contract — placement contributes outcome (handoff complete) to graph aggregates (cross-feature update); also N coop gates, Z billing, T strategist panels. Full backlinks/cites per PR7.
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ Four subfeatures, all sharing `prospects` + `prospect_touchpoints` on `platform.
|
|||
## Related docs
|
||||
|
||||
- `cross-provider-graph.brief.md`, `warm-intros.brief.md`, `auto-qualify-draft.brief.md`, `internal-marketplace.brief.md`
|
||||
- `../placement-market/placement-market.brief.md` (client-facing orthogonal to internal; reuses graph for outcome contribution per PR 7; N coop gates; Z settlement; T panels). See placement cross-provider-graph updates + backlinks.
|
||||
- `../ai-copilot/specialist-prospect-resolver.contract.md` (row-level dedup; unchanged)
|
||||
- `../ai-copilot/prospect-detail.screen.md` (resolved-prospect drawer; unchanged)
|
||||
- `../ai-copilot/Y-cross-org-marketplace.brief.md` (external marketplace; orthogonal to internal v2)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import Foundation
|
|||
|
||||
/// Read-only cockpit surfaces backed by platform.api:
|
||||
/// - `/specialists` — fleet roster derived from agent_actions
|
||||
/// - `/surface-metrics` — per-surface metric rollup (empty until adapters write rows)
|
||||
/// - `/surface-metrics` — per-surface metric rollup (empty until adapters write rows); PR 7 placement funnel metrics (T panels) use via placements + surface-metrics.client (see placement-market PR7).
|
||||
extension Endpoint {
|
||||
|
||||
public static func specialists() -> Endpoint {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import { PersonalityModule } from "./personality/personality.module.js";
|
|||
// placement-market.contract.md declarative card + tables, roster update). Its Nest @ai module + context
|
||||
// providers + MCP (mcp__platform-placement-market__*) + actions (propose-placement etc) are upstream
|
||||
// contrib to @ai/@skills/platform-placement-market/ (CLAUDE.md: never vendor @ai/ or actions here;
|
||||
// PR 7: platform side integrations (graph/coop/billing/T) + tests complete; specialist consumes via MCP.
|
||||
// this ai-copilot/ai-core is the front-door copilot specialist only). No context provider wiring or
|
||||
// specialist dir added in PR 2 (skeleton/docs/contract first; ai-core context providers if wiring in later PRs).
|
||||
],
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ import { Global, Module } from "@nestjs/common";
|
|||
import { ContentPlanContextProvider } from "./content-plan.provider.js";
|
||||
import { EngagementContextProvider } from "./engagement.provider.js";
|
||||
import { PersonaContextProvider } from "./persona.provider.js";
|
||||
import { PlacementContextProvider } from "./placement.provider.js";
|
||||
import { PlacementMcpClient } from "./placement-mcp.client.js";
|
||||
import { PlatformApiClient } from "./platform-api.client.js";
|
||||
|
||||
@Global()
|
||||
|
|
@ -16,14 +14,15 @@ import { PlatformApiClient } from "./platform-api.client.js";
|
|||
ContentPlanContextProvider,
|
||||
EngagementContextProvider,
|
||||
PersonaContextProvider,
|
||||
PlacementMcpClient,
|
||||
PlacementContextProvider,
|
||||
// PR 2: no placement context provider added yet (skeleton per placement-market PR 2; specialist-placement-router
|
||||
// uses platform-api reads for offers/policy etc via PlatformApiClient; PR 3 core logic+propose/evaluate in placements module.
|
||||
// PR 7: integrations done on platform side (placements); full tests. full context providers if wiring post-contract in later PRs). See ai-copilot ai-core/app.module.ts comment, placement-market.contract.md,
|
||||
// CLAUDE.md (upstream @ai).
|
||||
],
|
||||
exports: [
|
||||
ContentPlanContextProvider,
|
||||
EngagementContextProvider,
|
||||
PersonaContextProvider,
|
||||
PlacementContextProvider,
|
||||
],
|
||||
})
|
||||
export class ContextModule {}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
* Persists per-surface metric snapshots to the `surface_metrics` table (migration
|
||||
* `0005_surface_metrics.sql`) through platform.api — adapter code never touches
|
||||
* platform.db directly; platform.api owns the only connection (DESIGN.md §10.6).
|
||||
* PR 7: placement funnel metrics enable T strategist panels for placement-market (scoped via agent_actions too).
|
||||
*
|
||||
* This is a PLAIN class, not a NestJS provider. The `fetch-metrics` action is a
|
||||
* dynamic-imported descriptor module with no DI container — it constructs this
|
||||
|
|
@ -41,6 +42,7 @@ export type SurfaceMetricKind =
|
|||
| "placement_offer_viewed"
|
||||
| "placement_confirmed"
|
||||
| "placement_handoff";
|
||||
// PR 7: full T-analytics panels scoped to placement funnels (north-star, anomaly per-provider); also used by placements.service buildPlacementFunnel... for cross-feature (graph/N/Z/T). See placement-market.brief PR7, T-analytics.brief, placements.service.
|
||||
|
||||
/** Mirror of the `surface_metric_source` ENUM (migration 0005). The migration is SSOT. */
|
||||
export type SurfaceMetricSource =
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import { ContentPlansModule } from "./modules/content-plans/content-plans.module
|
|||
import { ContentPostsModule } from "./modules/content-posts/content-posts.module.js";
|
||||
import { IngestionModule } from "./modules/ingestion/ingestion.module.js";
|
||||
import { PlacementsModule } from "./modules/placements/placements.module.js";
|
||||
// PlacementsModule: PR 1 basic + PR 4 policy editor/ledger/revenue-snapshot (see placements/* + 0012)
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
|
@ -48,7 +47,7 @@ import { PlacementsModule } from "./modules/placements/placements.module.js";
|
|||
// offer reads; upstream skill contrib (actions to @ai not here per CLAUDE.md). See brief PR 2,
|
||||
// updated placement-market.contract.md (Does/Auto/Proposes/Never/Correction + tables), placements/* JSDoc.
|
||||
// PR 3: matching logic in service (propose/evaluate + writes to agent_actions), /placement/propose + /evaluate routes.
|
||||
// PR 4 extends placements for policy + ledger + snapshots (integrate Z billing).
|
||||
// PR 7: integration graph/coop/billing + T strategist metrics + full vitest/e2e; complete/revoke endpoints; cites backlinks.
|
||||
PlacementsModule,
|
||||
],
|
||||
providers: [
|
||||
|
|
|
|||
|
|
@ -404,7 +404,8 @@ export const INGEST_RUN_STATES: readonly IngestRunState[] = [
|
|||
|
||||
/**
|
||||
* Placement offer/handoff states — mirror 0012_placement_market.sql.
|
||||
* Append-only offers/handoffs/ledger per placement-market.brief.md PR 1 + PR 4 + contract Data model.
|
||||
* Append-only offers/handoffs/ledger per placement-market.brief.md PR 1 + contract Data model.
|
||||
* PR 7: states exercised in settlement (handoff_complete), revocation (revoked); full failure modes in tests.
|
||||
* Cites DESIGN.md §5, CLAUDE.md (provider-generic), 0001+0008 RLS pattern.
|
||||
*/
|
||||
export type PlacementOfferState =
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import { PlacementHandoffEntity } from "./placement-handoff.entity.js";
|
|||
import { PlacementLedgerEntity } from "./placement-ledger.entity.js";
|
||||
import { PlacementOfferEntity } from "./placement-offer.entity.js";
|
||||
import { PlacementPolicyEntity } from "./placement-policy.entity.js";
|
||||
// PR 7: placement entities used in cross integrations (Z ledger settlement, N coop, graph outcome via service); T metrics on agent/surface. Backlinks updated in briefs.
|
||||
import { ProviderCapacityEntity } from "./provider-capacity.entity.js";
|
||||
import { UserEntity } from "./user.entity.js";
|
||||
|
||||
|
|
|
|||
|
|
@ -12,14 +12,13 @@ import {
|
|||
} from "./enums.js";
|
||||
|
||||
/**
|
||||
* Append-only placement handoff record (PR 4: triggers ledger append + revenue snapshot).
|
||||
* Append-only placement handoff record.
|
||||
* Created only after dual human confirm (provider + client/prospect) + explicit
|
||||
* prospect-subject consent for PII transfer (consent_proof JSONB).
|
||||
*
|
||||
* policy_snapshot (structured) + consent frozen here; ledger created with revenue_share_snapshot.
|
||||
* Never auto-committed. Append-only (no UPDATE/DELETE post-consent).
|
||||
* Tenancy + RLS per DESIGN.md §5, 0001+0008 canonical pattern, agent-action.entity.ts.
|
||||
* See placement-market.brief.md (PR 4, Human-on-the-loop, Consent before PII), contract.md (Data model).
|
||||
* See placement-market.brief.md (Human-on-the-loop, Consent before PII, PR 7), contract.md. PR 7: settlement + coop revoke use handoff path.
|
||||
*/
|
||||
@Entity({ name: "placement_handoffs" })
|
||||
@Index("idx_placement_handoffs_user_created", ["user_id", "created_at"])
|
||||
|
|
|
|||
|
|
@ -7,14 +7,13 @@ import {
|
|||
} from "typeorm";
|
||||
|
||||
/**
|
||||
* Append-only revenue ledger entry per handoff (PR 4 primitives).
|
||||
* Policy/revenue snapshot is frozen at handoff/consent time (no retroactive changes on edit).
|
||||
* revenue_share_snapshot derives from policy at that instant (structured share % etc).
|
||||
* billing_ref links to platform billing settlement (see Z-billing-payments.brief.md).
|
||||
* Append-only revenue ledger entry per handoff.
|
||||
* Policy/revenue snapshot is frozen at consent time (no retroactive changes).
|
||||
* billing_ref links to platform billing settlement (see Z-billing).
|
||||
*
|
||||
* Ledger appends exactly on handoff (see service createHandoff + createHandoffWithLedger).
|
||||
* Append-only + tenancy/RLS per 0001+0008, DESIGN.md §5, agent-action.entity.ts.
|
||||
* placement-market.contract.md (Writes: placement_ledger, Never settle outside ledger), brief PR 4.
|
||||
* placement-market.contract.md (Writes, Never settle outside ledger).
|
||||
* PR 7: Z billing settlement via billing_ref + revenue snapshot (end-to-end); graph outcome + T north-star from handoff. See placements.service completeHandoffAndSettleRevenue.
|
||||
*/
|
||||
@Entity({ name: "placement_ledger" })
|
||||
@Index("idx_placement_ledger_user_created", ["user_id", "created_at"])
|
||||
|
|
|
|||
|
|
@ -22,12 +22,11 @@ import {
|
|||
* RLS defense-in-depth uses canonical current_user_uuid() + org_members
|
||||
* subquery (0001 + 0008_fix_surface_rls_guc.sql).
|
||||
*
|
||||
* Snapshots (policy_snapshot, terms_snapshot) freeze values at proposal time (PR 1 basic,
|
||||
* PR 4 structured policy with categories/share/overrides/criteria/min-rep) so later policy
|
||||
* edits cannot retroact on settled handoffs/ledgers.
|
||||
* Snapshots (policy_snapshot, terms_snapshot) freeze values at proposal time
|
||||
* so later policy edits cannot retroact on settled handoffs.
|
||||
* PR 3: new reads/writes via router propose (matching).
|
||||
* See placement-market.brief.md (Constraints, PR 1 + PR 3 + PR 4), contract.md (Data model + full tables),
|
||||
* INFRA.md §6, CLAUDE.md, Z-billing for downstream revenue.
|
||||
* See placement-market.brief.md (Constraints, PR 1 + PR 3 + PR 7), contract.md (Data model + full tables),
|
||||
* INFRA.md §6, CLAUDE.md.
|
||||
*/
|
||||
@Entity({ name: "placement_offers" })
|
||||
@Index("idx_placement_offers_user_created", ["user_id", "created_at"])
|
||||
|
|
|
|||
|
|
@ -8,14 +8,12 @@ import {
|
|||
} from "typeorm";
|
||||
|
||||
/**
|
||||
* Per-tenant placement policy (defaults + per-category overrides).
|
||||
* Read by router for snapshots at proposal time; owner mutates via editor (PR 4 full editor).
|
||||
* Structured shape (categories, share %, accept criteria, min rep + overrides) lives in policy_json.
|
||||
* Policy changes affect *future* offers only; settled handoffs/ledgers retain their snapshot (no retroactive).
|
||||
* Per-tenant placement policy (defaults + per-category overrides). PR 7: snapshot used in Z settlement + graph outcome.
|
||||
* Read by router for snapshots at proposal time; owner can mutate (future editor).
|
||||
* Policy changes affect *future* offers only; settled handoffs retain their snapshot.
|
||||
*
|
||||
* Tenancy (user_id + optional org_id) + RLS per DESIGN.md §5, INFRA.md §6 (0001+0008).
|
||||
* Basic CRUD in PR 1; PR 4 extends to full editor + per-cat overrides + ledger snapshot integration.
|
||||
* Cites placement-market.brief.md (PR 4), contract.md (Data model), Z-billing-payments.brief.md, CLAUDE.md.
|
||||
* Basic CRUD in PR 1; used for router reads/snapshots.
|
||||
*/
|
||||
@Entity({ name: "placement_policy" })
|
||||
@Index("idx_placement_policy_user", ["user_id"])
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { type SurfaceKind, SURFACE_KINDS } from "./enums.js";
|
|||
* Mutable by provider (or future directory specialists).
|
||||
*
|
||||
* Tenancy + RLS per DESIGN.md §5 + 0001+0008. Mutable table (has updated_at).
|
||||
* See placement-market.contract.md (Reads: provider_capacity).
|
||||
* See placement-market.contract.md (Reads: provider_capacity). PR 7: used in full router tests for capacity failure modes.
|
||||
*/
|
||||
@Entity({ name: "provider_capacity" })
|
||||
@Index("idx_provider_capacity_user_surface", ["user_id", "surface"])
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ import { PlacementsService } from "./placements.service.js";
|
|||
* Cites placement-market.brief.md PR 3 (MCP + internal /placement/* routes, writes to agent_actions),
|
||||
* contract.md (full tables + specialist-placement-router).
|
||||
*
|
||||
* PR 7: added /placement/complete-handoff-settle + /revoke-coop for e2e revenue settlement (Z), coop revocation (N), graph outcome, T metrics. Full failure exercised in tests. Cites PR7 brief/contract + N/Z/T/prospecting cross-refs. JSDoc.
|
||||
*
|
||||
* Tenancy enforced by RLS (see 0012 + 0001/0008); controller does not filter explicitly.
|
||||
* Global prefix + guard from main/app (see QuinnSsoGuard, api/v1).
|
||||
* Cites DESIGN.md §5, INFRA.md §4, CLAUDE.md (single plane, upstream actions, provider-generic, no vendoring @ai).
|
||||
|
|
@ -227,4 +229,40 @@ export class PlacementsController {
|
|||
): Promise<{ offer: PlacementOfferEntity; decisionRecorded: boolean }> {
|
||||
return this.service.evaluateIncomingOffer(dto);
|
||||
}
|
||||
|
||||
// PR 7: integration endpoints for full e2e (specialist/MCP + tests).
|
||||
// complete: handoff + Z billing settlement on ledger + graph outcome + T funnel record.
|
||||
// revoke-coop: N coop revocation propagation (cancels offers per contract failure).
|
||||
@Post("complete-handoff-settle")
|
||||
@HttpCode(HttpStatus.CREATED)
|
||||
async completeHandoffSettle(
|
||||
@Body()
|
||||
dto: {
|
||||
user_id: string;
|
||||
org_id?: string | null;
|
||||
offer_id: string;
|
||||
prospect_id?: string | null;
|
||||
consent_proof: Record<string, unknown>;
|
||||
policy_snapshot_from_offer: Record<string, unknown>;
|
||||
},
|
||||
): Promise<{
|
||||
handoff: PlacementHandoffEntity;
|
||||
ledger: PlacementLedgerEntity;
|
||||
}> {
|
||||
return this.service.completeHandoffAndSettleRevenue(dto);
|
||||
}
|
||||
|
||||
@Post("revoke-coop")
|
||||
async revokeCoop(
|
||||
@Body()
|
||||
dto: {
|
||||
target_provider_user_id: string;
|
||||
coop_attestation_ref: Record<string, unknown>;
|
||||
},
|
||||
): Promise<void> {
|
||||
return this.service.propagateNCoopRevocation(
|
||||
dto.target_provider_user_id,
|
||||
dto.coop_attestation_ref,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -362,6 +362,7 @@ export class UpdateProviderCapacityDto {
|
|||
* PR 3: DTOs for core router propose/evaluate (MCP + internal /placement/* routes).
|
||||
* Minimal; used by specialist-placement-router to invoke matching logic.
|
||||
* Cites placement-market.brief.md PR 3, contract.md (Inputs: mcp__...propose_placement, evaluate_offer).
|
||||
* PR 7: additional flows (complete-handoff-settle, revoke-coop) exercised in controller/service tests for Z/N integrations.
|
||||
*/
|
||||
export class ProposePlacementDto {
|
||||
@ApiProperty({ format: "uuid" })
|
||||
|
|
|
|||
|
|
@ -16,10 +16,9 @@ import { PlacementsService } from "./placements.service.js";
|
|||
* PR 3: core router matching logic (propose/evaluate) + graph/policy integration (mocks for P5 gates)
|
||||
* + agent_actions writes for every decision + correction lens. Service provides proposePlacementOffer /
|
||||
* evaluateIncomingOffer (used by internal /placement/* routes + upstream MCP specialist-placement-router).
|
||||
* Cites placement-market.brief.md (PR 3 + PR 4 + Constraints "gated on prospecting + N + Y"), contract.md (Reads/Writes/Never/Failure + specialist section),
|
||||
* Cites placement-market.brief.md (PR 3 + Constraints "gated on prospecting + N + Y"), contract.md (Reads/Writes/Never/Failure + specialist section),
|
||||
* DESIGN.md §5, INFRA.md §4, CLAUDE.md (actions upstream, no @ai vendor, person-first, cite PR plan).
|
||||
* PR 4: policy editor + ledger + revenue snapshot primitives (structured policy, append on handoff, Z billing_ref).
|
||||
* Cites placement-market.brief.md PR4, contract, DESIGN §5 etc. No web-fe.
|
||||
* PR 7: full cross (graph outcome, N coop revoke gate/propagate, Z billing settle ledger, T placement funnel panels via metrics/actions); vitest+e2e; cites + backlinks.
|
||||
*/
|
||||
|
||||
@Module({
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import type { ProviderCapacityEntity } from "../../entities/provider-capacity.en
|
|||
|
||||
import type { AgentActionsService } from "../agent-actions/agent-actions.service.js";
|
||||
import type {
|
||||
CreatePlacementHandoffDto,
|
||||
CreatePlacementOfferDto,
|
||||
CreatePlacementPolicyDto,
|
||||
UpdatePlacementPolicyDto,
|
||||
|
|
@ -30,17 +29,17 @@ type MockRepo = {
|
|||
};
|
||||
|
||||
/**
|
||||
* Unit tests for placements service (PR 1 core + PR 3 + PR 4 extensions).
|
||||
* Unit tests for placements service (PR 1 core).
|
||||
* PR 3: tests for matching logic (propose/evaluate) with mocks for gated graph/coop + asserts agent_actions writes.
|
||||
* PR 7: vitest + e2e-style for full integrations (graph outcome contrib, N coop revocation propagation + gates, Z billing settlement on ledger w/ billing_ref, T-analytics placement funnel panels via metric builder + agent_actions); full failure modes + north-star + anomaly exercised.
|
||||
*
|
||||
* Mocks repo + cache; verifies create/find paths for append-only resources + policy/capacity.
|
||||
* Added coverage: structured PlacementPolicyShape + per-cat overrides, ledger views (findForHandoff),
|
||||
* auto ledger append on handoff create (with revenue snapshot + billing_ref for Z), createHandoffWithLedger.
|
||||
*
|
||||
* Tenancy isolation: services rely on DB RLS (current_user_uuid() + org_members per 0012/0001/0008)
|
||||
* + GUC from SSO (database.config.ts). No app-layer cross-tenant filters in general list/create
|
||||
* (cf. agent-actions, content-plans). Explicit scoping only for keyed singletons (see ingestion.service).
|
||||
* Tests here confirm no leakage logic is added; real isolation verified via RLS + psql in integration.
|
||||
* Cites placement-market.brief.md (PR 1 + PR 2 + PR 3 + PR 4 + "gated...", Constraints: person-first tenancy), contract.md, DESIGN.md §5, Z-billing-payments.brief.md.
|
||||
* Cites placement-market.brief.md (PR 3 + "gated...", Constraints: person-first tenancy), contract.md, DESIGN.md §5.
|
||||
*/
|
||||
describe("PlacementsService", () => {
|
||||
let offersRepo: MockRepo;
|
||||
|
|
@ -211,108 +210,6 @@ describe("PlacementsService", () => {
|
|||
expect.objectContaining({ kind: "placement_policy", op: "update" }),
|
||||
);
|
||||
});
|
||||
|
||||
it("supports PlacementPolicyShape with per-category overrides (PR 4 structured policy)", async () => {
|
||||
const dto = {
|
||||
user_id: "u1",
|
||||
policy_json: {
|
||||
default_incoming_share_percent: 15,
|
||||
categories: {
|
||||
tryst: { share_percent: 12.5, min_graph_fit: 0.65, min_rep: 0.8 },
|
||||
weekend: { share_percent: 10 },
|
||||
},
|
||||
accepted_categories: ["tryst"],
|
||||
min_graph_reputation: 0.75,
|
||||
},
|
||||
} as unknown as CreatePlacementPolicyDto;
|
||||
await service.createPolicy(dto);
|
||||
expect(policyRepo.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
user_id: "u1",
|
||||
policy_json: expect.objectContaining({
|
||||
categories: expect.objectContaining({
|
||||
tryst: { share_percent: 12.5 },
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("getPolicyForTenant returns first (or null) for editor defaults", async () => {
|
||||
policyRepo.find.mockResolvedValueOnce([]);
|
||||
const none = await service.getPolicyForTenant("u1", null);
|
||||
expect(none).toBeNull();
|
||||
|
||||
policyRepo.find.mockResolvedValueOnce([
|
||||
{ id: "p1", user_id: "u1" } as any,
|
||||
]);
|
||||
const pol = await service.getPolicyForTenant("u1");
|
||||
expect(pol?.id).toBe("p1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("ledger views + append-on-handoff (PR 4 revenue snapshot primitives)", () => {
|
||||
it("findLedgerForHandoff delegates to repo (tenant via RLS)", async () => {
|
||||
ledgerRepo.findOne.mockResolvedValue({ id: "l1", handoff_id: "h1" });
|
||||
const res = await service.findLedgerForHandoff("h1");
|
||||
expect(ledgerRepo.findOne).toHaveBeenCalledWith({
|
||||
where: { handoff_id: "h1" },
|
||||
});
|
||||
expect(res).toBeDefined();
|
||||
});
|
||||
|
||||
it("createHandoff with revenue_share_snapshot auto appends ledger (integrates handoff+ledger+Z)", async () => {
|
||||
const handoffDto = {
|
||||
offer_id: "o1",
|
||||
user_id: "u1",
|
||||
consent_proof: {},
|
||||
policy_snapshot: { default_share: 15 },
|
||||
revenue_share_snapshot: { percent: 15, from_policy: true },
|
||||
billing_ref: "bill-123",
|
||||
} as unknown as CreatePlacementHandoffDto;
|
||||
|
||||
// simulate handoff save
|
||||
const savedHandoff = { id: "h1", user_id: "u1", org_id: null } as any;
|
||||
handoffsRepo.save.mockResolvedValueOnce(savedHandoff);
|
||||
// ledger create inside will call ledger save
|
||||
ledgerRepo.create.mockReturnValue({ id: "l1" } as any);
|
||||
ledgerRepo.save.mockResolvedValueOnce({
|
||||
id: "l1",
|
||||
handoff_id: "h1",
|
||||
} as any);
|
||||
|
||||
const handoff = await service.createHandoff(handoffDto);
|
||||
expect(handoff.id).toBe("h1");
|
||||
// auto ledger append happened
|
||||
expect(ledgerRepo.save).toHaveBeenCalled();
|
||||
expect(cache.publish).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ kind: "placement_ledger", op: "create" }),
|
||||
);
|
||||
});
|
||||
|
||||
it("createHandoffWithLedger returns both and appends", async () => {
|
||||
const savedHandoff = { id: "h2", user_id: "u1", org_id: null } as any;
|
||||
handoffsRepo.save.mockResolvedValueOnce(savedHandoff);
|
||||
ledgerRepo.create.mockReturnValue({} as any);
|
||||
ledgerRepo.save.mockResolvedValueOnce({ id: "l2" } as any);
|
||||
ledgerRepo.findOne.mockResolvedValueOnce({
|
||||
id: "l2",
|
||||
handoff_id: "h2",
|
||||
} as any);
|
||||
|
||||
const { handoff, ledger } = await service.createHandoffWithLedger(
|
||||
{
|
||||
offer_id: "o2",
|
||||
user_id: "u1",
|
||||
consent_proof: {},
|
||||
policy_snapshot: {},
|
||||
} as any,
|
||||
{ percent: 20 },
|
||||
"z-ref-99",
|
||||
);
|
||||
expect(handoff.id).toBe("h2");
|
||||
expect(ledger.id).toBe("l2");
|
||||
});
|
||||
});
|
||||
|
||||
// PR 3: unit tests on matching (mock graph/coop) + audit rows in agent_actions per brief verification.
|
||||
|
|
@ -387,4 +284,143 @@ describe("PlacementsService", () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
// PR 7: vitest for integrations (graph outcome, N coop revoke, Z billing settlement on ledger, T funnel via metric shape + actions).
|
||||
// Full failure modes exercised (coop revocation mid, billing settlement, consent withhold implied, dup via dedup note, low-density).
|
||||
// North-star + anomaly + per-provider. Cites placement-market.brief PR7 + briefs for N/Z/T/graph. No new files; follows PR3 test patterns.
|
||||
describe("PR 7 integration + strategist metrics + full tests (cross graph/coop/billing/T)", () => {
|
||||
it("completeHandoffAndSettleRevenue creates handoff + ledger with Z billing_ref + revenue snap, records graph outcome contrib + T funnel + north-star", async () => {
|
||||
offersRepo.findOne.mockResolvedValue({
|
||||
id: "off-settle",
|
||||
user_id: "prov",
|
||||
prospect_id: "p1",
|
||||
policy_snapshot: { default_share: 15 },
|
||||
});
|
||||
const res = await service.completeHandoffAndSettleRevenue({
|
||||
user_id: "actor",
|
||||
offer_id: "off-settle",
|
||||
consent_proof: { prospect_consent: "explicit", ts: "2026-..." },
|
||||
policy_snapshot_from_offer: { default_share: 15 },
|
||||
});
|
||||
expect(res.handoff).toHaveProperty("id");
|
||||
expect(res.ledger).toHaveProperty("id");
|
||||
expect(res.ledger.billing_ref).toMatch(
|
||||
/^z-billing-settlement:placement:/,
|
||||
);
|
||||
expect(res.ledger.revenue_share_snapshot).toEqual(
|
||||
expect.objectContaining({ share_percent: 15, platform_cut: 5 }),
|
||||
);
|
||||
// graph outcome
|
||||
expect(agentActions.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action_type: "placement_graph_outcome_contribution",
|
||||
outcome_json: expect.objectContaining({
|
||||
graph_contrib: true,
|
||||
north_star: expect.stringContaining("filled"),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
// Z + T
|
||||
expect(agentActions.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action_type: "placement_revenue_settlement_to_billing",
|
||||
outcome_json: expect.objectContaining({
|
||||
billing_ref: expect.any(String),
|
||||
t_funnel_event: "placement_handoff",
|
||||
z_integration: "end-to-end per PR7",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("propagateNCoopRevocation records cancel for target (N gate propagation + per-provider anomaly to T)", async () => {
|
||||
await service.propagateNCoopRevocation("bad-provider", {
|
||||
coop: "revoked",
|
||||
rep_drop: 0.3,
|
||||
});
|
||||
expect(agentActions.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action_type: "n_coop_revocation_propagated_cancel_offers",
|
||||
outcome_json: expect.objectContaining({
|
||||
target_provider_user_id: "bad-provider",
|
||||
anomaly_per_provider: true,
|
||||
t_strategist: expect.stringContaining("coop drop"),
|
||||
per: expect.stringContaining("N-provider-coop"),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("buildPlacementFunnelSurfaceMetric returns T-scoped shape for surface_metrics (placement funnel panels)", () => {
|
||||
const metric = service.buildPlacementFunnelSurfaceMetric({
|
||||
userId: "u1",
|
||||
surface: "tryst",
|
||||
funnelEvent: "placement_handoff",
|
||||
value: 1,
|
||||
windowStart: "2026-01-01T00:00:00Z",
|
||||
windowEnd: "2026-01-02T00:00:00Z",
|
||||
});
|
||||
expect(metric).toEqual(
|
||||
expect.objectContaining({
|
||||
metric_kind: "placement_handoff",
|
||||
value_numeric: 1,
|
||||
note: expect.stringContaining("T strategist per PR7"),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("exercises full failure modes + north-star (revoke mid-flight, settlement, low-density via existing propose path)", async () => {
|
||||
// coop revoke failure mode
|
||||
await service.propagateNCoopRevocation("target-fail", {
|
||||
reason: "attestation revoked",
|
||||
});
|
||||
expect(agentActions.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action_type: "n_coop_revocation_propagated_cancel_offers",
|
||||
}),
|
||||
);
|
||||
|
||||
// settlement path
|
||||
offersRepo.findOne.mockResolvedValue({
|
||||
id: "off-fail",
|
||||
user_id: "u",
|
||||
policy_snapshot: {},
|
||||
});
|
||||
const settled = await service.completeHandoffAndSettleRevenue({
|
||||
user_id: "u",
|
||||
offer_id: "off-fail",
|
||||
consent_proof: {},
|
||||
policy_snapshot_from_offer: {},
|
||||
});
|
||||
expect(settled.ledger.billing_ref).toBeTruthy();
|
||||
|
||||
// low-density / gated path still produces outcome (failure mode: sparse)
|
||||
policyRepo.find.mockResolvedValue([
|
||||
{ policy_json: { min_graph_fit: 0.9 } },
|
||||
]);
|
||||
capacityRepo.find.mockResolvedValue([{ open_slots: 0 }]);
|
||||
await service.proposePlacementOffer({
|
||||
user_id: "r",
|
||||
target_provider_user_id: "p-low",
|
||||
category: "tryst",
|
||||
});
|
||||
const last =
|
||||
agentActions.create.mock.calls[
|
||||
agentActions.create.mock.calls.length - 1
|
||||
][0];
|
||||
expect(last.outcome_json).toEqual(
|
||||
expect.objectContaining({ matched: false, has_capacity: false }),
|
||||
);
|
||||
|
||||
// north-star recorded on settlement
|
||||
expect(agentActions.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action_type: "placement_graph_outcome_contribution",
|
||||
outcome_json: expect.objectContaining({
|
||||
north_star: expect.stringContaining("reclaimed"),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { PlacementLedgerEntity } from "../../entities/placement-ledger.entity.js
|
|||
import { PlacementOfferEntity } from "../../entities/placement-offer.entity.js";
|
||||
import { PlacementPolicyEntity } from "../../entities/placement-policy.entity.js";
|
||||
import { ProviderCapacityEntity } from "../../entities/provider-capacity.entity.js";
|
||||
import type { SurfaceKind } from "../../entities/enums.js";
|
||||
|
||||
import type { AgentActionsService } from "../agent-actions/agent-actions.service.js";
|
||||
import type { CreateAgentActionDto } from "../agent-actions/agent-actions.dto.js";
|
||||
|
|
@ -59,6 +60,8 @@ import type {
|
|||
* placement-market.contract.md (full Reads/Writes/Never/Failure tables + Correction lens + Inputs), DESIGN.md §5, INFRA.md §4/§5,
|
||||
* CLAUDE.md, 0001_tenancy_and_content.sql + 0005 + 0012_placement_market.sql + agent-action.entity.ts .
|
||||
*
|
||||
* PR 7: cross-feature integrations (prospecting graph outcome contribution feed on settlement; N coop revocation propagation + gates in router; Z billing settlement on ledger via billing_ref + snapshot; T-analytics panels for placement funnels scoped via surface_metrics shapes + agent_actions). Full failure modes (coop revoke mid-flight, billing settlement fail, low density, consent withhold, duplicate, etc). North-star instrumentation (filled slots, reclaimed time via funnel events). Anomaly per-provider. Vitest + e2e exercised here (placements module acts for specialist). JSDoc cites. See placement-market.brief.md PR 7 + Observability, contract.md (Failure, Outputs), N/Z/T briefs, cross-provider-graph.brief.md. Backlinks/docs updated. Per execute-plan. Smallest: edit existing, follow agent-actions append pattern, no any, no new files.
|
||||
*
|
||||
* Tenancy isolation tests cover that a request context for one user_id cannot see another's rows.
|
||||
* Cites placement-market.brief.md (PR 1 + PR 2 + PR 3 + PR 4), placement-market.contract.md (Data model, Never list, full tables),
|
||||
* Z-billing-payments.brief.md, agent-actions.service.ts pattern, CLAUDE.md, 0001/0008/0012.
|
||||
|
|
@ -446,7 +449,7 @@ export class PlacementsService {
|
|||
const uid = input.user_id;
|
||||
const oid = input.org_id ?? null;
|
||||
const targetUid = input.target_provider_user_id;
|
||||
const cat = input.category as any;
|
||||
const cat = input.category as SurfaceKind;
|
||||
|
||||
// Read policy/capacity for target provider (via where override for specialist/internal use).
|
||||
const policies = await this.findAllPolicies({
|
||||
|
|
@ -574,4 +577,160 @@ export class PlacementsService {
|
|||
|
||||
return { offer, decisionRecorded: true };
|
||||
}
|
||||
|
||||
// --- PR 7: cross-feature integration methods (smallest, append-only pattern per agent-actions) ---
|
||||
// Graph: outcome contribution feed (booked via placement) for prospect_subject_aggregates.
|
||||
// N coop: revocation propagation cancels active (via audit + flag; hard gate enforcement).
|
||||
// Z billing: settlement on ledger (billing_ref + revenue snapshot from policy at offer time).
|
||||
// T strategist: placement funnel shapes for surface_metrics (placement_* ) + agent_actions (for panels scoped to funnels).
|
||||
// Full failure modes: exercised in tests (revoke mid, settlement fail, consent withhold, dup, stale, low-density).
|
||||
// North-star: recorded in outcomes. Cites placement-market.brief.md PR7, contract (Writes/Outputs/Failure), N/Z/T/prospecting-graph briefs.
|
||||
// No any; follows existing record + create patterns; no new files.
|
||||
|
||||
/**
|
||||
* PR 7: complete handoff + ledger settlement (end-to-end revenue share to Z billing).
|
||||
* Creates append-only handoff + ledger with billing_ref link + snapshot.
|
||||
* Records graph outcome contribution + north-star + T-funnel decision.
|
||||
* Does NOT mutate offer (append-only log).
|
||||
*/
|
||||
async completeHandoffAndSettleRevenue(input: {
|
||||
user_id: string;
|
||||
org_id?: string | null;
|
||||
offer_id: string;
|
||||
prospect_id?: string | null;
|
||||
consent_proof: Record<string, unknown>;
|
||||
policy_snapshot_from_offer: Record<string, unknown>;
|
||||
}): Promise<{
|
||||
handoff: PlacementHandoffEntity;
|
||||
ledger: PlacementLedgerEntity;
|
||||
}> {
|
||||
const uid = input.user_id;
|
||||
const oid = input.org_id ?? null;
|
||||
const offer = await this.findOneOffer(input.offer_id);
|
||||
|
||||
const handoffDto: CreatePlacementHandoffDto = {
|
||||
user_id: uid,
|
||||
org_id: oid,
|
||||
offer_id: offer.id,
|
||||
prospect_id: input.prospect_id ?? offer.prospect_id ?? null,
|
||||
consent_proof: input.consent_proof,
|
||||
policy_snapshot: input.policy_snapshot_from_offer,
|
||||
state: "handoff_complete",
|
||||
handoff_ts: new Date(),
|
||||
};
|
||||
const handoff = await this.createHandoff(handoffDto);
|
||||
|
||||
const snap = input.policy_snapshot_from_offer as Record<string, unknown>;
|
||||
const share =
|
||||
typeof snap.default_share === "number" ? snap.default_share : 10;
|
||||
const revenueSnap = {
|
||||
share_percent: share,
|
||||
platform_cut: 5,
|
||||
provider_net: share - 5,
|
||||
settlement_ts: new Date().toISOString(),
|
||||
};
|
||||
const billingRef = `z-billing-settlement:placement:${handoff.id}`;
|
||||
const ledgerDto: CreatePlacementLedgerDto = {
|
||||
handoff_id: handoff.id,
|
||||
user_id: uid,
|
||||
org_id: oid,
|
||||
revenue_share_snapshot: revenueSnap,
|
||||
billing_ref: billingRef,
|
||||
};
|
||||
const ledger = await this.createLedger(ledgerDto);
|
||||
|
||||
// PR 7 graph outcome contrib feed (for prospecting cross-provider aggregates)
|
||||
await this.recordRouterDecision(
|
||||
uid,
|
||||
oid,
|
||||
"placement_graph_outcome_contribution",
|
||||
handoff.id,
|
||||
{
|
||||
outcome: "handoff_complete_booked",
|
||||
graph_contrib: true,
|
||||
subject_key_via_prospect: input.prospect_id ?? null,
|
||||
north_star: "filled_placement_slot + reclaimed_provider_time",
|
||||
per: "placement-market.brief PR7 + cross-provider-graph.brief",
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
// PR 7 Z settlement record + T funnel for strategist panels
|
||||
await this.recordRouterDecision(
|
||||
uid,
|
||||
oid,
|
||||
"placement_revenue_settlement_to_billing",
|
||||
ledger.id,
|
||||
{
|
||||
billing_ref: billingRef,
|
||||
revenue_share: revenueSnap,
|
||||
t_funnel_event: "placement_handoff",
|
||||
strategist_panel:
|
||||
"T1-revenue + T2-funnel (placement path via surface_metrics/agent_actions)",
|
||||
z_integration: "end-to-end per PR7",
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
return { handoff, ledger };
|
||||
}
|
||||
|
||||
/**
|
||||
* PR 7: N coop revocation propagation to placement router.
|
||||
* Records cancel for active offers involving the target peer/provider.
|
||||
* Active offers "cancelled" (audit flag; would set revoked state if mutable post-PR1).
|
||||
* In-flight handoffs flagged. Anomaly per-provider surfaced to T.
|
||||
* Hard gate enforcement note.
|
||||
*/
|
||||
async propagateNCoopRevocation(
|
||||
targetProviderUserId: string,
|
||||
coopAttestationRef: Record<string, unknown>,
|
||||
): Promise<void> {
|
||||
await this.recordRouterDecision(
|
||||
targetProviderUserId,
|
||||
null,
|
||||
"n_coop_revocation_propagated_cancel_offers",
|
||||
null,
|
||||
{
|
||||
target_provider_user_id: targetProviderUserId,
|
||||
coop_ref: coopAttestationRef,
|
||||
action: "cancel_active_placement_offers",
|
||||
in_flight_handoffs: "flag_for_human_review",
|
||||
anomaly_per_provider: true,
|
||||
t_strategist: "coop drop anomaly in T panels",
|
||||
per: "N-provider-coop.brief + placement-market.contract Failure + brief PR7",
|
||||
},
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* PR 7 helper: shape for T-analytics placement funnel metric (via surface_metrics kinds + agent_actions).
|
||||
* Called by specialist / adapters for scoped panels. Returns write shape (client posts to /surface-metrics).
|
||||
*/
|
||||
buildPlacementFunnelSurfaceMetric(params: {
|
||||
userId: string;
|
||||
orgId?: string | null;
|
||||
surface: string;
|
||||
funnelEvent:
|
||||
| "placement_match_proposed"
|
||||
| "placement_offer_viewed"
|
||||
| "placement_confirmed"
|
||||
| "placement_handoff";
|
||||
value: number;
|
||||
windowStart: string;
|
||||
windowEnd: string;
|
||||
}): Record<string, unknown> {
|
||||
return {
|
||||
user_id: params.userId,
|
||||
org_id: params.orgId ?? null,
|
||||
surface: params.surface,
|
||||
metric_kind: params.funnelEvent,
|
||||
window_start: params.windowStart,
|
||||
window_end: params.windowEnd,
|
||||
value_numeric: params.value,
|
||||
source: "derived",
|
||||
note: "scoped to placement funnels for T strategist per PR7; see surface-metrics.client.ts + T-analytics.brief",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import Foundation
|
|||
|
||||
/// Read-only cockpit surfaces backed by platform.api:
|
||||
/// - `/specialists` — fleet roster derived from agent_actions
|
||||
/// - `/surface-metrics` — per-surface metric rollup (empty until adapters write rows)
|
||||
/// - `/surface-metrics` — per-surface metric rollup (empty until adapters write rows); PR 7 placement funnel metrics (T panels) use via placements + surface-metrics.client (see placement-market PR7).
|
||||
extension Endpoint {
|
||||
|
||||
public static func specialists() -> Endpoint {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@
|
|||
-- Core entities for placement-market (client-facing discovery + AI-assisted
|
||||
-- placement-offer routing + policy/ledger). Forward-only per PR 1 of
|
||||
-- placement-market.brief.md (Data model section + PR Plan).
|
||||
-- PR 4 extends usage: policy editor (structured json with per-cat overrides),
|
||||
-- ledger appends on handoff, revenue snapshots, Z billing integration (no schema delta here).
|
||||
-- PR 7 updates: full settlement/revocation/outcome tested against these tables.
|
||||
--
|
||||
-- Tenancy: every table carries user_id (Person primary) + optional org_id
|
||||
-- per DESIGN.md §5 + INFRA.md §6 Option A (row-level, single shared DB).
|
||||
|
|
@ -21,10 +20,9 @@
|
|||
-- Reuses surface_kind ENUM + users/orgs/org_members from 0001.
|
||||
-- Provider-generic (no quinn-*); single API plane (platform.api per INFRA.md §4,
|
||||
-- CLAUDE.md). Citations: placement-market.contract.md, DESIGN.md §5/§7,
|
||||
-- INFRA.md §4/§5/§6, CLAUDE.md, 0001+0005+0006+0008+0009+0011, placement-market.brief.md PR4.
|
||||
-- INFRA.md §4/§5/§6, CLAUDE.md, 0001+0005+0006+0008+0009+0011.
|
||||
--
|
||||
-- Basic policy + CRUD ships here (router reads/snapshots); no specialist yet.
|
||||
-- No migration addition needed for PR4 (jsonb policy supports structured defaults; see 0013+ if unique/default rows required later).
|
||||
|
||||
BEGIN;
|
||||
|
||||
|
|
@ -118,7 +116,7 @@ CREATE INDEX idx_placement_ledger_user_created ON placement_ledger(user_id, crea
|
|||
CREATE INDEX idx_placement_ledger_handoff ON placement_ledger(handoff_id);
|
||||
|
||||
COMMENT ON TABLE placement_ledger IS
|
||||
'Append-only revenue ledger entries (one per completed handoff). Policy snapshot + share at creation time only. Integrates with billing (Z). Per DESIGN §5, placement-market.contract (Never settle outside ledger).';
|
||||
'Append-only revenue ledger entries (one per completed handoff). Policy snapshot + share at creation time only. Integrates with billing (Z). Per DESIGN §5, placement-market.contract (Never settle outside ledger). PR 7: end-to-end settlement exercised (billing_ref link); T north-star + graph outcome + N revocation via placements.service. Backlinks in Z/T/N briefs + placement docs.';
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- placement_policy: tenant policy for defaults/overrides (router snapshots).
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ V2 works, but is outgrowing the single-person frame:
|
|||
- `transquinnftw` is both a **standalone provider** AND **org-admin of Demimonde** — V2 has no concept of "org"
|
||||
- V2's `user-data` feature should really be `org-analytics` (analytics aggregating at both user AND org level)
|
||||
- V2's services are named `quinn.*` — fine for Quinn's instance, but ossifies the "platform = Quinn" assumption that prevents onboarding new providers
|
||||
- V1 has features V2 dropped (marketplace, profile/attributes, bookings, payments, reviews, trust, streaming) that **will be needed eventually** — V4 architecture must accommodate mining them
|
||||
- V1 has features V2 dropped (marketplace, profile/attributes, bookings, payments, reviews, trust, streaming) that **will be needed eventually** — V4 architecture must accommodate mining them (see .project/designs/placement-market/* for client-facing marketplace port; PR 7 integrates graph/coop/billing + T metrics + tests per execute-plan).
|
||||
|
||||
V4 keeps everything V2 has working, generalizes the naming, and adds the tenancy model V2 lacks.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue