cursor: Backlog — Owner-experience: unify chat + docs + board into o - #600
cursor: Backlog — Owner-experience: unify chat + docs + board into o#600servathadi wants to merge 8 commits into
Conversation
Design-first backlog for the owner-experience artifact: one relationship (Talk/Know/Watch) bound to a project outcome, earned autonomy, honest receipts-by-id. Locks invariants in owner-experience/v1 + pure helpers/tests. Names unmet deps (Tier-2 BLOCK, Port-4 dormant, ModelPort v2). No route/UI. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5ae130838d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const verifiedWinCount = input.wins.filter( | ||
| (win) => win.verification === 'resolved_by_id' && win.receiptId.trim().length > 0, | ||
| ).length |
There was a problem hiding this comment.
Count distinct receipts before widening autonomy
When the same resolved receipt is supplied repeatedly, each array entry increments verifiedWinCount, so three copies of one win satisfy the default three-win threshold and can widen autonomy. Count unique normalized receipt IDs or reject duplicates so a single verified outcome cannot be replayed as multiple wins.
Useful? React with 👍 / 👎.
| export function decideProgressDisplay(input: { | ||
| metric: OutcomeMetricSpec | null | ||
| sourceWired: boolean | ||
| signal: { ok: true; value: number } | { ok: false; reason: string } | null | ||
| }): ProgressDisplay { |
There was a problem hiding this comment.
Bind the signal to the selected metric source
When a caller accidentally supplies a signal from a different source—for example, a task-count value for a github_prs outcome—the helper has only an untagged number and sourceWired: true, so it returns measured progress despite the contract explicitly forbidding progress from an unrelated signal. Carry the signal's source ID and fail closed unless it matches metric.sourceId.
Useful? React with 👍 / 👎.
| const value = input.signal.value | ||
| const ratio = Math.min(1, Math.max(0, value / target)) | ||
| return { kind: 'measured', value, target, ratio } |
There was a problem hiding this comment.
Reject non-finite progress values
When a wired KPI calculation returns NaN or an infinite value while marked successful, this path emits kind: 'measured' with a non-finite raw value and potentially a NaN ratio; an infinite target is likewise accepted. This can render invalid progress or serialize the ratio as null rather than reporting unavailable, so both target and value should be checked with Number.isFinite before producing measured progress.
Useful? React with 👍 / 👎.
| | `workflow_receipts` + verdicts + evidence projections | `writeReceiptToD1`, `src/projects/projections.ts` | Honest win/miss store (resolve by **receipt id**, never free-string label) | | ||
| | `lessons_capture` receipt | `src/projects/completion-gate.ts` | First visible-learning feed (project terminal) | | ||
| | Agent `Autonomy` enum | `src/types.ts` `suggest\|draft\|execute\|execute_with_approval` | Trust strip vocabulary — **static dial today** | | ||
| | `autonomyImpliesGate` | `src/org/service.ts` | Gate implication already enforced on task create | |
There was a problem hiding this comment.
Enforce approval autonomy on every task creation path
The reviewed tree does not enforce this implication in the canonical task-create path: autonomyImpliesGate has only one caller in src/agents/loop.ts, while createTask accepts gate_owner from its caller and defaults it to null. Consequently, API, MCP, or Talk code can assign and dispatch work to an execute_with_approval agent without a gate, so the design cannot rely on the owner holding every approval until this is wired into the canonical path or every permitted dispatch path is explicitly constrained.
Useful? React with 👍 / 👎.
| | **Goal** | Outcome strip | New structured fields (slice 1) | | ||
| | **Sense** | Watch situation + metric read | `loadProjectSituation` + KPI source | | ||
| | **Rank** | What moves the number next | BrainPort ranking (rank-not-act); learning bias **only after** Port-4+ranker PASS | | ||
| | **Act** | Board tasks / flights through technicians | Existing board + BYOA | | ||
| | **Receipt** | Win or miss, by receipt id | `workflow_receipts` / verdicts / evidence | |
There was a problem hiding this comment.
Add project attribution to the execution loop
The inspected runGoalCycle in src/agents/loop.ts neither accepts a project ID nor passes project_id to createTask, which therefore stores its autonomous work as unattributed, while ProjectSituation filters tasks by the current project ID. As a result, the proposed Sense→Rank→Act loop cannot make mubot-driven work appear in the project's Watch surface or project receipts, and none of the listed build slices adds the missing project-scoped execution wiring.
Useful? React with 👍 / 👎.
| -- additive on projects (shape only; slice 1 owns the migration) | ||
| outcome_statement TEXT NOT NULL DEFAULT '' -- may mirror goal initially | ||
| outcome_metric_json TEXT NULL -- KpiSpec or null |
There was a problem hiding this comment.
Keep the legacy goal and Outcome in one source of truth
Adding outcome_statement with an empty default while retaining the independently writable projects.goal creates two goal sources without a required backfill or synchronization rule. Existing projects can therefore show an empty Outcome, and after an owner changes it, seedTaskFromGoal, the concierge dispatcher, project search, and ProjectSituation will continue reading and acting on the stale legacy goal; either migrate all readers to Outcome or require an atomic backfill and bidirectional compatibility policy.
Useful? React with 👍 / 👎.
Same label-pattern fix as ranker. Wins carry polarity/project/agent; projectId+agentId required on widen input; consumeReceiptIds blocks ladder reuse. mayMubot* take callerKind. Hold push until 5ae1308 lenses land. Co-authored-by: Cursor <cursoragent@cursor.com>
…ust-locks Decision: Talk optional until #505 revived. BrainPort out of reuse (rankTasks interim). Progress: Outcome + KpiSourceId, no sourceWired, v1 unmeasured. Widen: dedup + minRequiredWins=3 + VerifiedWinRef resolver + scope + consume. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 70127a5ff4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| '_brand' in win && | ||
| (win as VerifiedWinRef)._brand === 'VerifiedWinRef' |
There was a problem hiding this comment.
Resolve receipts inside the autonomy gate
When a caller supplies { _brand: 'VerifiedWinRef', receiptId, projectId, agentId, polarity: 'win', resolvedAt }, this check accepts it without ever invoking WinReceiptResolver; VerifiedWinRef is an exported structural type with a public string brand, so such an object is valid TypeScript and trivial to construct from request data. Three fabricated objects can therefore satisfy the earned-win threshold and authorize a widen. Accept receipt IDs and perform the trusted lookup inside the decision path, or use runtime provenance that callers cannot manufacture.
Useful? React with 👍 / 👎.
| autonomy_widen_events ( | ||
| id TEXT PRIMARY KEY, | ||
| project_id TEXT NOT NULL, | ||
| agent_id TEXT NOT NULL, | ||
| from_autonomy TEXT NOT NULL, |
There was a problem hiding this comment.
Scope the effective autonomy value by project
When an agent's squad has access to multiple projects, this project-scoped audit event cannot keep the widen confined to the project whose wins earned it: the effective autonomy value currently lives on the global agents.autonomy column and runGoalCycle reads that value without project context, while this new table stores only audit history. Applying a project A widen through the existing autonomy write therefore also enables the higher level for project B. Add a project-agent autonomy binding and use it during execution, or define the widen as global and require appropriately global evidence.
Useful? React with 👍 / 👎.
| 5. **Visible learning** — lesson list from `lessons_capture` + gate-fail | ||
| receipts by id on Watch. |
There was a problem hiding this comment.
Store actual lesson content before building the lesson list
When the visible-learning slice is implemented from the current tree, lessons_capture cannot supply the promised lesson rows: recordLessonsCapture stores only the project ID, principal, completed-to-archived transition, and timestamp, with no polarity or summary, and a repo-wide search finds no separate lesson store containing them. Consequently this slice can show that capture occurred but cannot render what was learned as specified; add a receipted lesson payload/store before treating lessons_capture as the feed.
Useful? React with 👍 / 👎.
| if (input.outcome.measurementMode === 'unmeasured_until_project_kpi') { | ||
| return { kind: 'unmeasured' } | ||
| } | ||
| if (input.outcome.metric === null) return { kind: 'unmeasured' } | ||
| if (!isKpiSourceId(input.outcome.metric.sourceId)) return { kind: 'unmeasured' } |
There was a problem hiding this comment.
Derive measured mode from a project-scoped source
When a caller sets measurementMode: 'measured', this branch permits a measured result even though the only current KpiSourceId values (task_counter and github_prs) are explicitly agent/tenant scoped rather than project scoped. A matching source ID therefore still lets an agent's KPI appear as the project's Outcome progress, bypassing the v1 unmeasured_until_project_kpi rule through what is effectively another caller-controlled wiring flag. Until a project-scoped source exists, force this helper to return unmeasured, or derive eligibility from a resolver that verifies the signal's project scope.
Useful? React with 👍 / 👎.
Same defect class as ranker VerifiedReceiptRef — cosmetic _brand was forgeable. Mint registers in WeakSet + Object.freeze; gate checks registry. Reproduce-and-refuse: forged brand → unverified_win_label. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fb76b2f5f6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| receiptId: string | ||
| projectId: string | ||
| agentId: string | ||
| polarity: 'win' | 'miss' | ||
| resolvedAt: string |
There was a problem hiding this comment.
Define wins using authoritative receipt fields
When the earned-autonomy slice wires this resolver to workflow_receipts, it cannot construct this record from authoritative columns: migrations/0012_workflow_pipeline.sql stores only id, task, step, status, detail, and timestamp, while migrations/0059_project_projection_keysets.sql adds only project_id; there is no agentId or polarity. Without a specified authoritative join and win-classification rule, production must either leave widening unwired or infer security-sensitive wins from ad hoc receipt details, so define that schema/resolution path before treating these records as verified.
Useful? React with 👍 / 👎.
| const receiptId = draft.receiptId.trim() | ||
| if (!receiptId) return { ok: false, reason: 'receipt_id_required' } |
There was a problem hiding this comment.
Resolve lesson IDs before accepting drafts
When a caller supplies any nonempty string such as the test's nonexistent rcpt-9, this helper accepts it without checking a receipt store, allowing an unbacked “lesson” despite the design's requirement that lessons resolve by receipt ID and never be self-reported without a backing receipt. Accept a resolver-backed verified receipt reference, or perform the lookup here, rather than treating string presence as provenance.
Useful? React with 👍 / 👎.
| current: Autonomy | ||
| proposed: Autonomy | ||
| actingPrincipalKind: PermissionCallerKind | ||
| wins: readonly (VerifiedWinRef | LabeledWinRef)[] |
There was a problem hiding this comment.
Bind widen decisions to an authenticated principal
When the confirm path is built, the gate receives only the caller-selected category actingPrincipalKind, so an agent-originated call can claim owner_or_admin_human without supplying an authenticated principal ID or proving that principal is an owner/admin for this project. The successful decision also cannot populate the design's required autonomy_widen_events.decided_by field from gate-verified data; pass a trusted principal identity and verify its project role inside the decision path instead of authorizing from this label.
Useful? React with 👍 / 👎.
| export interface Outcome { | ||
| statement: string | ||
| metric: OutcomeMetricSpec | null | ||
| /** v1: existing agent KPI sources cannot measure a project → unmeasured. */ | ||
| measurementMode: 'unmeasured_until_project_kpi' | 'measured' |
There was a problem hiding this comment.
Include ownership metadata in the Outcome contract
When slice 1 uses this exported type as the structured Outcome contract, a conforming value has no owner_principal, set_at, or version, even though the specified model requires all three and relies on version increments for owner changes. The write path therefore cannot use this contract to attribute an update, preserve its timestamp, or detect stale concurrent edits; include these fields here or explicitly separate the persisted Outcome type from this display-only input.
Useful? React with 👍 / 👎.
BLOCK-B residual: measurementMode:'measured' could be produced by any caller string on an allowlisted-but-wrong-scope KpiSourceId (task_counter/github_prs are agent/tenant scoped, not project scoped). Add PROJECT_SCOPED_KPI_SOURCE_IDS (empty in v1) and gate 'measured' on membership, making it unreachable in v1 by construction rather than by convention. Delete the green test that asserted a measured ratio from github_prs; add tests proving 'measured' is unreachable for every allowlisted KpiSourceId, and a retitled test that a non-allowlisted id cast is refused (G7). Doc sync across design.md: #505 is closed-unmerged (not "in review"), Rank maps to the live rankTasks interim (not BrainPort, which is type-only-sealed with zero callers), and §4.2/§4.5/§8 now describe unmeasured_until_project_kpi semantics consistently. §4.2's Outcome diagram matches the shipped TS type including measurementMode. Cheap riders: contract status assertion checks allowlist membership instead of a literal; OWNER_TRUST_LADDER drift-locked against the newly-exported AUTONOMIES; migration 0072 named as next-free; new JSON fields (minRequiredWins, homeRequiredFacets, projectScopedKpiSourceIds, winConstructor) asserted against their TS constants instead of duplicated literals; suggestedBuildOrder documented in §8 prose; added a mutation-style test for the callerKind invalid-enum throw. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2088e4c0c0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| from_autonomy TEXT NOT NULL, | ||
| to_autonomy TEXT NOT NULL, | ||
| win_receipt_ids TEXT NOT NULL, -- JSON array of receipt ids | ||
| decided_by TEXT NOT NULL, -- owner principal |
There was a problem hiding this comment.
Persist receipt consumption atomically
When two widen confirmations using the same wins run concurrently, both can read the receipts as unconsumed, pass decideEarnedAutonomyWiden, and insert events because win_receipt_ids is only JSON with no uniqueness constraint. This violates the stated consumption invariant even with correct callers; store one row per consumed receipt under a unique key, or combine a compare-and-set autonomy update with atomic receipt claims so only one confirmation can succeed.
Useful? React with 👍 / 👎.
| 4. **Earned autonomy** — `autonomy_widen_events` + propose/confirm path using | ||
| contract gate; capability grants still go through existing | ||
| `grant_agent_capability`. |
There was a problem hiding this comment.
Route existing autonomy edits through the earned gate
When this slice lands, an admin can still bypass every verified-win check by posting autonomy=execute to the existing /agents/:id/config route: src/dashboard/index.ts:1115 calls updateUnitConfig, whose autonomy branch at src/org/service.ts:663-666 validates only enum membership. The proposed confirm path therefore does not make autonomy earned unless every elevation path—including direct config edits and elevated initial creation—is routed through this gate or restricted to narrowing.
Useful? React with 👍 / 👎.
Direct isKpiSourceId + KPI_SOURCE_IDS contents assertions replace a false comment claiming independence from the project-scope gate. Freeze both allowlists so "unreachable by construction" is not defeasible by push. Co-authored-by: Cursor <cursoragent@cursor.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Files in this head: - src/contracts/contract-assert-policy.ts (new — approved helper) - tests/owner-experience-contract.test.ts (real-suite policy + isFrozen locks) - src/types.ts (Object.freeze(AUTONOMIES)) - docs/superpowers/specs/2026-07-27-owner-experience-unified-surface-design.md (slice-1 brief: ship four measured-branch tests with first project-scoped source) Co-authored-by: Cursor <cursoragent@cursor.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
| } | ||
|
|
||
| function mirrorLiteralPattern(accessPath: string): RegExp { | ||
| const escaped = accessPath.replace(/\./g, '\\.') |
| } | ||
|
|
||
| function mirrorUsesTsConstant(testSource: string, accessPath: string, tsExportName: string): boolean { | ||
| const escapedPath = accessPath.replace(/\./g, '\\.') |
Files: - src/contracts/contract-assert-policy.ts — blob-identical to policy lane - tests/contract-assert-policy.test.ts + fixtures (was left behind) - docs/contract-assert-policy.md — lint-not-enforcement + blob ownership Co-authored-by: Cursor <cursoragent@cursor.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Dispatched to the
cursoragent (Grok) headless via the mupot loop for task80ca4d66-5779-4ee5-b5c4-2960f90100c0.Task done-when: A design + build unifies the three owner surfaces (chat=talk to mubot, docs=read/edit its knowledge, board=watch it work) into one coherent goal-directed experience per the owner-experience artifact: owner declares an OUTCOME (north-star metric) -> mubot drives the loop toward it, learning + adapting -> owner sees goal-progress + earned-trust level + what the mubot learned; trust widens with verified wins (earned autonomy); owner owns goal + gate, mubot owns execution + adaptation. Ties the chat (Tier-2), docs (#522-526), and lifecycle/situation surfaces together.
Driver verified: cursor committed real work +
tsc --noEmitclean. Kasra-core gates this PR before merge (the task is inreview; cursor cannot self-close it).