mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-27 02:44:50 +01:00
Bind Phase 6 corpus to production behavior
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
a392a69f87
commit
33d9e8ef66
15 files changed
+1349
-224
No files matched your search
@@ -1,6 +1,6 @@
|
||||
# Phase 6 Thin Paperclip Adapter Boundary
|
||||
|
||||
Status: design approved; implementation gate pending CTO re-review
|
||||
Status: design approved; implementation remediated; CTO conformance re-review pending
|
||||
Date: 2026-08-09
|
||||
Decision scope: the first feature-flagged Paperclip native run, including the
|
||||
normative native finalizer and server-owned Section 18 status arbitration
|
||||
@@ -759,17 +759,43 @@ committer, finalizer, and terminal heartbeat projection. No package contract,
|
||||
provider/session behavior, legacy adapter, approval authority, workspace
|
||||
policy, or UI surface changes.
|
||||
|
||||
The corpus test remains in its pre-approved matrix path. It now creates a
|
||||
fixture-specific database shape and dispatches the fixture to its applicable
|
||||
production consumers (mode selection, durable-evidence classification,
|
||||
arbitration/commit, interaction mediation, recovery policy, and
|
||||
migration/compatibility reads). The language-neutral corpus vocabulary is
|
||||
normalized by an input-fact-keyed test oracle that never reads `expected`.
|
||||
Every expected status/preserve action, reason, required/forbidden effect,
|
||||
live-path kind, and native-record flag is then compared without filtering; a
|
||||
separate mutation check changes each assertion category for every fixture and
|
||||
must fail. The 70 matrix rows join those stored fixture observations instead
|
||||
of invoking a generic consumer again.
|
||||
### Remediation 3 production-policy and corpus amendment (2026-08-09)
|
||||
|
||||
The following production files were already listed in the approved authority
|
||||
seams above. This amendment explicitly permits corpus-conformance vocabulary
|
||||
and return-shape corrections inside those seams; it grants no new authority:
|
||||
|
||||
```text
|
||||
server/src/services/native-runtime/status-arbiter.ts
|
||||
server/src/services/native-runtime/status-decision-committer.ts
|
||||
server/src/services/native-runtime/native-run-finalizer.ts
|
||||
server/src/services/native-runtime/native-interaction-bridge.ts
|
||||
server/src/services/native-runtime/native-session-executor.ts
|
||||
server/src/services/native-runtime/native-finalization-reconciler.ts
|
||||
server/src/services/native-runtime/runtime-mode.ts
|
||||
```
|
||||
|
||||
The arbiter remains the versioned server-owned policy boundary. The finalizer,
|
||||
attention, cancellation, reconciliation, compatibility, and migration modules
|
||||
expose trigger-specific production consumers of that policy. The committer
|
||||
continues to materialize decisions and effects atomically through existing
|
||||
issue, interaction, wake, recovery, and activity services. This amendment does
|
||||
not authorize changes to package contracts, provider/session behavior, a
|
||||
concrete legacy adapter, approval authority, workspace or budget policy, or UI.
|
||||
|
||||
The corpus test remains in its pre-approved matrix path. It creates a
|
||||
fixture-specific database shape and dispatches the fixture to the responsible
|
||||
production consumer. All eleven expected fields are derived from consumer
|
||||
return values and persisted production rows: run status, status/preserve
|
||||
action, reason, required and forbidden effects, live-path kind, claim
|
||||
preservation, native-record behavior, decision count, maximum wake count, and
|
||||
maximum notification count. A separate mutation check changes every expected
|
||||
field for every fixture and must fail. Every one of the 70 matrix row IDs maps
|
||||
to a named finalizer, terminal projection, attention, cancellation, committer,
|
||||
reconciliation, compatibility, or migration consumer, and the row fails if
|
||||
that consumer did not execute or returned different semantics. No hand-written
|
||||
test policy table supplies observed outcomes and no row is satisfied by a
|
||||
coverage-label join to a generic observation.
|
||||
|
||||
## Commands the implementation must make runnable
|
||||
|
||||
|
||||
@@ -93,12 +93,18 @@ native canary: it executes one selected task through the public package session
|
||||
contract and applies one server-owned decision. Its post-kill-switch legacy
|
||||
case has zero native history rows.
|
||||
|
||||
The Section 18.13 database test executes fixture-specific consumer paths for
|
||||
all 52 fixtures, compares every status/preserve action, reason,
|
||||
required/forbidden effect, live-path kind, and native-record flag without an
|
||||
assertion filter, and joins the 70 unique matrix rows to those observations. A
|
||||
mutation check independently changes every assertion category for each fixture
|
||||
and proves the comparison rejects it.
|
||||
The Section 18.13 database test executes fixture-specific production consumers
|
||||
for all 52 fixtures. It derives and checks all eleven expected fields from
|
||||
consumer return values and persisted production rows: run status,
|
||||
status/preserve action, reason, required and forbidden effects, live-path kind,
|
||||
claim preservation, native-record behavior, decision count, maximum wake
|
||||
count, and maximum notification count. Each of the 70 unique matrix rows names
|
||||
the responsible finalizer, terminal projection, attention, cancellation,
|
||||
committer, reconciliation, compatibility, or migration consumer and fails if
|
||||
that consumer did not execute or returned different semantics. A per-fixture
|
||||
mutation check independently changes every expected field and proves the
|
||||
comparison rejects it; there is no test-owned policy table supplying observed
|
||||
semantics.
|
||||
|
||||
Result-less same-run recovery is also proven through the production heartbeat
|
||||
seam: a persisted envelope/checkpoint is leased by `reapOrphanedRuns`, enters
|
||||
|
||||
@@ -68,11 +68,17 @@ same-run pre-result resumption, lease races, and audit output. All database test
|
||||
start embedded PostgreSQL and do not skip when a developer database URL is
|
||||
absent.
|
||||
|
||||
The corpus cases dispatch fixture-specific database shapes to the applicable
|
||||
production consumers and compare every expected semantic field; the 70 matrix
|
||||
rows join those executions. The recovery case uses a scripted provider only at
|
||||
the backend boundary while the real heartbeat reaper, lease claim, original
|
||||
run execution, persistence port, finalizer, and terminal projection execute.
|
||||
The corpus cases dispatch fixture-specific database shapes to named production
|
||||
consumers and derive all eleven expected fields from their returns and
|
||||
persisted rows: run status, status/preserve action, reason, required and
|
||||
forbidden effects, live-path kind, claim preservation, native-record behavior,
|
||||
decision count, maximum wake count, and maximum notification count. Every one
|
||||
of the 70 matrix rows requires evidence from its responsible finalizer,
|
||||
terminal projection, attention, cancellation, committer, reconciliation,
|
||||
compatibility, or migration consumer; a missing call or changed return fails
|
||||
the row. The recovery case uses a scripted provider only at the backend
|
||||
boundary while the real heartbeat reaper, lease claim, original run execution,
|
||||
persistence port, finalizer, and terminal projection execute.
|
||||
|
||||
The command above is the repeatable scripted internal canary and actual
|
||||
flag-off legacy-heartbeat proof. It is not a live-provider canary. The remaining
|
||||
|
||||
@@ -4,7 +4,7 @@ title: Phase 6 thin Paperclip adapter verification
|
||||
description: Targeted package, database-port, finalization, security-boundary, feature-selection, and legacy fallback evidence.
|
||||
tags: [native-runner, phase-6, verification, paperclip, replay, finalization]
|
||||
status: stable
|
||||
generated: { by: openai/gpt-5.6, at: 2026-08-09T03:01:00Z }
|
||||
generated: { by: openai/gpt-5.6, at: 2026-08-09T05:20:00Z }
|
||||
---
|
||||
|
||||
# Scope
|
||||
@@ -13,7 +13,7 @@ This record verifies the default-off Phase 6 tracer at the approved
|
||||
`ControlPlanePort`/`NativeSessionBackend` boundary. No browser surface changed,
|
||||
so Phase 6 has command/database evidence rather than screenshots.
|
||||
|
||||
The counts below are the PAP-16875 remediation rerun. “Internal canary” means
|
||||
The counts below are the PAP-16877 remediation rerun. “Internal canary” means
|
||||
the selected-task database test through the public package session contract;
|
||||
it does not mean that a new live Codex provider task was dispatched.
|
||||
|
||||
@@ -104,14 +104,19 @@ pnpm --filter @paperclipai/server exec vitest run \
|
||||
This gate proves concurrent event allocation, duplicate-only migration repair,
|
||||
bounded retry exhaustion, actual flag-off legacy execution, and executable
|
||||
Section 18.13 fixture-to-consumer traceability. Each of the 52 fixtures creates
|
||||
its own database shape and executes applicable production consumers; every
|
||||
expected status/preserve action, reason, required/forbidden effect, live-path
|
||||
kind, and native-record flag is compared without filtering. The 70 unique
|
||||
matrix rows join those fixture observations, and a mutation test changes each
|
||||
assertion category for every fixture to prove that the comparison fails. The
|
||||
selected-task and recovery canaries remain scripted only at the provider
|
||||
boundary, while the production Paperclip persistence/finalization paths and the
|
||||
flag-off legacy adapter/finalizer path execute for real.
|
||||
its own database shape and executes named production consumers. Their return
|
||||
values and persisted rows supply all eleven expected fields: run status,
|
||||
status/preserve action, reason, required and forbidden effects, live-path kind,
|
||||
claim preservation, native-record behavior, decision count, maximum wake
|
||||
count, and maximum notification count. Each of the 70 matrix rows has an
|
||||
explicit responsible finalizer, terminal projection, attention, cancellation,
|
||||
committer, reconciliation, compatibility, or migration consumer; the row fails
|
||||
if that consumer was not executed or its returned semantics differ. A mutation
|
||||
test changes every expected field for every fixture and proves comparison
|
||||
failure. No test-owned policy table supplies an observation. The selected-task
|
||||
and recovery canaries remain scripted only at the provider boundary, while the
|
||||
production Paperclip persistence/finalization paths and the flag-off legacy
|
||||
adapter/finalizer path execute for real.
|
||||
|
||||
# Compile and migration checks
|
||||
|
||||
|
||||
@@ -62,10 +62,13 @@ existing server authority and keep legacy execution as the default.
|
||||
applied the complete migration; the actual flag-off heartbeat executed the
|
||||
legacy adapter/finalizer and created zero native rows.
|
||||
- Section 18.13 source corpus: 52 fixture-specific database executions passed
|
||||
through their applicable production consumers, all expected semantic fields
|
||||
were compared without filtering, and the 70 unique matrix rows joined those
|
||||
observations. Per-fixture mutation checks reject changed status, reason,
|
||||
required/forbidden effect, live-path, and native-record assertions.
|
||||
through named production consumers. Consumer returns and persisted rows
|
||||
supplied all eleven assertions: run status, status/preserve action, reason,
|
||||
required/forbidden effects, live path, claim preservation, native-record
|
||||
behavior, decision count, maximum wake count, and maximum notification
|
||||
count. All 70 matrix rows require their named consumer execution and compare
|
||||
that consumer's semantics. Per-fixture mutation checks reject changes to
|
||||
every expected field; no test-owned policy table supplies observations.
|
||||
- The production heartbeat reaper resumed one persisted result-less run through
|
||||
the original execution/finalization path with the flag disabled. It recovered
|
||||
an already-active scripted provider turn and produced exactly one result,
|
||||
|
||||
@@ -5,11 +5,13 @@ import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import {
|
||||
agents,
|
||||
agentWakeupRequests,
|
||||
companies,
|
||||
completionContracts,
|
||||
createDb,
|
||||
heartbeatRuns,
|
||||
issueThreadInteractions,
|
||||
issueRecoveryActions,
|
||||
issueWorkProducts,
|
||||
issues,
|
||||
nativeRunFinalizations,
|
||||
@@ -23,18 +25,36 @@ import { classifyNativeEvidence } from "../services/native-runtime/evidence-clas
|
||||
import {
|
||||
materializeNativeInteractionResponses,
|
||||
rejectUnsupportedNativeRuntimeRequest,
|
||||
resolveNativeAttentionStatus,
|
||||
} from "../services/native-runtime/native-interaction-bridge.js";
|
||||
import {
|
||||
cancelNativeSession,
|
||||
nativeSessionFailureDisposition,
|
||||
resolveNativeCancellationStatus,
|
||||
} from "../services/native-runtime/native-session-executor.js";
|
||||
import { resolveNativeRuntimeMode } from "../services/native-runtime/runtime-mode.js";
|
||||
import { arbitrateNativeStatus } from "../services/native-runtime/status-arbiter.js";
|
||||
import {
|
||||
inspectNativeCompatibilityState,
|
||||
inspectNativeMigrationState,
|
||||
resolveNativeCompatibilityStatus,
|
||||
resolveNativeMigrationStatus,
|
||||
resolveNativeRuntimeMode,
|
||||
} from "../services/native-runtime/runtime-mode.js";
|
||||
import {
|
||||
arbitrateNativeStatus,
|
||||
type NativeStatusAuthorityScenario,
|
||||
} from "../services/native-runtime/status-arbiter.js";
|
||||
import {
|
||||
commitNativeStatusDecision,
|
||||
type NativeStatusCommitFailpoint,
|
||||
} from "../services/native-runtime/status-decision-committer.js";
|
||||
import {
|
||||
projectNativeTerminalRunStatus,
|
||||
recordNativeFinalizationFailure,
|
||||
resolveNativeFinalizerStatus,
|
||||
} from "../services/native-runtime/native-run-finalizer.js";
|
||||
import { resolveNativeReconciliationStatus } from "../services/native-runtime/native-finalization-reconciler.js";
|
||||
import { issueService } from "../services/issues.js";
|
||||
import { issueThreadInteractionService } from "../services/issue-thread-interactions.js";
|
||||
import { startEmbeddedPostgresTestDatabase } from "./helpers/embedded-postgres.js";
|
||||
|
||||
type Fixture = {
|
||||
@@ -44,22 +64,32 @@ type Fixture = {
|
||||
given: Record<string, unknown>;
|
||||
expected: {
|
||||
statusAction: string;
|
||||
runStatus: string;
|
||||
reasonCode: string | null;
|
||||
requiredEffects: string[];
|
||||
forbiddenEffects: string[];
|
||||
livePathKind: string | null;
|
||||
preserveClaim: boolean;
|
||||
nativeRecords: boolean;
|
||||
decisionCount: number;
|
||||
maxWakeCount: number;
|
||||
maxNotificationCount: number;
|
||||
};
|
||||
};
|
||||
|
||||
type Corpus = { schema: string; corpusRevision: number; fixtures: Fixture[] };
|
||||
|
||||
type PolicyObservation = {
|
||||
runStatus: string;
|
||||
statusAction: string;
|
||||
reasonCode: string | null;
|
||||
effects: string[];
|
||||
livePathKind: string | null;
|
||||
preserveClaim: boolean;
|
||||
nativeRecords: boolean;
|
||||
decisionCount: number;
|
||||
wakeCount: number;
|
||||
notificationCount: number;
|
||||
};
|
||||
|
||||
type ConsumerExecution = {
|
||||
@@ -70,8 +100,7 @@ type ConsumerExecution = {
|
||||
type FixtureObservation = PolicyObservation & {
|
||||
fixtureId: string;
|
||||
consumerExecutions: ConsumerExecution[];
|
||||
persistedDecisionCount: number;
|
||||
persistedEffectCount: number;
|
||||
consumerEvidenceByRow: Map<string, string>;
|
||||
};
|
||||
|
||||
const corpusPath = fileURLToPath(new URL(
|
||||
@@ -81,72 +110,83 @@ const corpusPath = fileURLToPath(new URL(
|
||||
|
||||
const corpus = JSON.parse(readFileSync(corpusPath, "utf8")) as Corpus;
|
||||
|
||||
/**
|
||||
* Independent test oracle for the Phase 5 scenario vocabulary. It is keyed by
|
||||
* input fact, never by fixture id or expected output, so editing an expected
|
||||
* field cannot also change the observation. Production consumers below must
|
||||
* execute before an oracle result is accepted.
|
||||
*/
|
||||
const policyByCompletionState: Record<string, Omit<PolicyObservation, "nativeRecords">> = {
|
||||
mechanically_satisfied: policy("done", "completion_contract_satisfied", ["release_checkout"]),
|
||||
low_risk_policy_claim: policy("done", "completion_claim_policy_accepted", ["release_checkout"]),
|
||||
missing_required_test: policy("in_progress", "completion_evidence_incomplete", ["enqueue_continuation"], "continuation"),
|
||||
no_durable_continuation: policy("preserve", "prior_status_preserved_no_live_path", ["record_finalization_error"], "recovery"),
|
||||
named_reviewer_required: policy("in_review", "completion_review_required", ["bind_reviewer", "notify_owner"], "review"),
|
||||
governed_gate_pending: policy("in_review", "governed_gate_pending", ["create_interaction", "notify_owner"], "interaction"),
|
||||
review_required: policy("in_review", "completion_review_required", ["bind_reviewer", "notify_owner"], "review"),
|
||||
safe_partial_parse: policy("preserve", "native_finalization_invalid", ["record_finalization_error"], "recovery"),
|
||||
valid_continuation: policy("in_progress", "live_continuation_registered", ["enqueue_continuation"], "continuation"),
|
||||
alternate_track_runnable: policy("in_progress", "turn_waiting_other_track_live", ["enqueue_continuation"], "continuation"),
|
||||
task_wide_owner_action_bound: policy("blocked", "task_wide_blocker_bound", ["bind_blocker", "notify_owner"], "blocker"),
|
||||
context_answer_current: policy("in_progress", "attention_resolved_from_context", ["enqueue_continuation"], "continuation"),
|
||||
ordinary_domain_expertise: policy("in_progress", "attention_routed_to_agent", ["create_delegated_issue", "enqueue_continuation"], "delegated_issue"),
|
||||
intentional_human_judgment: policy("in_review", "attention_requires_human_authority", ["create_interaction", "notify_owner"], "interaction"),
|
||||
equivalent_attention_family: policy("preserve", "attention_duplicate_suppressed", ["link_canonical_request"]),
|
||||
resolver_budget_exhausted: policy("preserve", "attention_budget_exhausted", ["record_finalization_error"], "recovery"),
|
||||
partial_evidence_persisted: policy("preserve", "run_failed_partial_evidence_preserved", ["schedule_retry"], "retry"),
|
||||
claim_before_workspace_failure: policy("preserve", "finalization_failed_claim_preserved", ["record_finalization_error"], "recovery"),
|
||||
turn_scope: policy("preserve", "cancellation_turn_only", ["accept_replacement_turn"], "continuation"),
|
||||
run_scope: policy("preserve", "cancellation_run_only", ["release_run_resources"]),
|
||||
issue_scope_authorized: policy("cancelled", "cancellation_issue_authorized", ["release_checkout", "cancel_continuations"]),
|
||||
new_evidence_satisfies_contract: policy("done", "decision_superseded_by_new_evidence", ["release_checkout"]),
|
||||
dependency_now_done: policy("in_progress", "dependency_resolved", ["enqueue_continuation"], "continuation"),
|
||||
explicit_resume_capability: policy("in_progress", "authorized_resume", ["enqueue_continuation"], "continuation"),
|
||||
replacement_turn_accepted: policy("preserve", null, ["accept_replacement_turn"], "continuation"),
|
||||
transient_retry_then_success: policy("in_progress", "attention_resolved_from_context", ["enqueue_continuation"], "continuation"),
|
||||
cross_company_target: policy("preserve", "result_schema_rejected", ["record_finalization_error"]),
|
||||
response_after_supersession: policy("preserve", "attention_duplicate_suppressed", ["record_stale_response"]),
|
||||
interaction_expired: policy("preserve", "attention_budget_exhausted", ["record_expiry"], "recovery"),
|
||||
identical_result_before_ack: policy("done", "completion_contract_satisfied", ["release_checkout"]),
|
||||
reused_id_changed_material: policy("preserve", "structured_result_replay_conflict", ["record_finalization_error"]),
|
||||
result_preserved: policy("done", "completion_contract_satisfied", ["resume_workspace_operation", "release_checkout"]),
|
||||
decision_committed_delivery_pending: policy("in_progress", "live_continuation_registered", ["enqueue_continuation", "dispatch_pending_effect"], "continuation"),
|
||||
board_cancelled_before_cas: policy("preserve", "prior_status_terminal_preserved", ["append_superseding_assessment"]),
|
||||
new_policy_requires_review: policy("in_review", "completion_review_required", ["bind_reviewer", "append_superseding_assessment"], "review"),
|
||||
legacy_exit_zero: policy("legacy_finalizer", null, ["legacy_existing_behavior"]),
|
||||
native_field_only_in_result_json: policy("legacy_finalizer", null, ["legacy_existing_behavior"]),
|
||||
preexisting_open_issue: policy("preserve", null, ["initialize_status_version_zero"], "review"),
|
||||
shadow_application_disabled: policy("preserve", "completion_contract_satisfied", ["record_shadow_decision"]),
|
||||
mixed_ledger: policy("preserve", "completion_contract_satisfied", ["render_four_layers"]),
|
||||
authorized_writer_incremented_version: policy("preserve", "arbitration_conflict_reloaded", ["increment_status_version", "schedule_reconciliation"], "recovery"),
|
||||
production_shaped_upgrade: policy("preserve", null, ["expand_schema", "status_version_default_zero"]),
|
||||
authorized_status_write: policy("in_review", null, ["increment_status_version_once", "bind_reviewer"], "review"),
|
||||
no_native_rows: policy("legacy_finalizer", null, ["return_native_false"]),
|
||||
shadow_compute: policy("preserve", "completion_contract_satisfied", ["materialize_contract", "record_shadow_decision"]),
|
||||
classified_native_legacy_divergence: policy("preserve", "completion_evidence_incomplete", ["record_mode_labeled_divergence"]),
|
||||
allowlisted_company_adapter_policy: policy("in_progress", "live_continuation_registered", ["enqueue_continuation", "record_mode_native"], "continuation"),
|
||||
cohort_policy_pinned: policy("done", "completion_contract_satisfied", ["record_mode_native", "record_policy_version"]),
|
||||
kill_switch_during_active_native_run: policy("in_progress", "live_continuation_registered", ["enqueue_continuation", "finish_as_native", "stop_new_native_dispatch"], "continuation"),
|
||||
no_reviewed_adapter_contract_migration: policy("legacy_finalizer", null, ["retain_legacy_mode", "retain_audit_lineage"]),
|
||||
const matrixRowConsumers: Record<string, string> = {
|
||||
"SD-01": "native-finalizer-status",
|
||||
"SD-02": "native-finalizer-status",
|
||||
"SD-03": "native-finalizer-status",
|
||||
"SD-04": "native-finalizer-status",
|
||||
"SD-05": "native-finalizer-status",
|
||||
"SD-06": "native-finalizer-status",
|
||||
"SD-07": "native-finalizer-status",
|
||||
"SD-08": "native-finalizer-status",
|
||||
"SD-09": "native-attention-resolver",
|
||||
"SD-10": "native-attention-resolver",
|
||||
"SD-11": "native-attention-resolver",
|
||||
"SD-12": "native-attention-resolver",
|
||||
"SD-13": "native-attention-resolver",
|
||||
"SD-14": "native-finalizer-status",
|
||||
"SD-15": "native-finalizer-status",
|
||||
"SD-16": "native-cancellation-authority",
|
||||
"SD-17": "native-cancellation-authority",
|
||||
"SD-18": "native-cancellation-authority",
|
||||
"SD-19": "native-reconciliation-consumer",
|
||||
"TC-01": "native-run-terminal-projection",
|
||||
"TC-02": "native-run-terminal-projection",
|
||||
"TC-03": "native-run-terminal-projection",
|
||||
"TC-04": "native-run-terminal-projection",
|
||||
"TC-05": "native-run-terminal-projection",
|
||||
"TC-06": "native-run-terminal-projection",
|
||||
"TC-07": "native-run-terminal-projection",
|
||||
"TC-08": "native-run-terminal-projection",
|
||||
"ATT-01": "native-attention-resolver",
|
||||
"ATT-02": "native-attention-resolver",
|
||||
"ATT-03": "native-attention-resolver",
|
||||
"ATT-04": "native-attention-resolver",
|
||||
"ATT-05": "native-attention-resolver",
|
||||
"ATT-06": "native-attention-resolver",
|
||||
"ATT-07": "native-attention-resolver",
|
||||
"ATT-08": "native-attention-resolver",
|
||||
"ATT-09": "native-attention-resolver",
|
||||
"ATT-10": "native-attention-resolver",
|
||||
"ATT-11": "native-attention-resolver",
|
||||
"ATT-12": "native-attention-resolver",
|
||||
"LIVE-01": "status-decision-committer",
|
||||
"LIVE-02": "status-decision-committer",
|
||||
"LIVE-03": "status-decision-committer",
|
||||
"LIVE-04": "status-decision-committer",
|
||||
"LIVE-05": "status-decision-committer",
|
||||
"LIVE-06": "status-decision-committer",
|
||||
"REC-01": "native-reconciliation-consumer",
|
||||
"REC-02": "native-reconciliation-consumer",
|
||||
"REC-03": "native-reconciliation-consumer",
|
||||
"REC-04": "native-reconciliation-consumer",
|
||||
"REC-05": "native-reconciliation-consumer",
|
||||
"REC-06": "native-reconciliation-consumer",
|
||||
"REC-07": "native-reconciliation-consumer",
|
||||
"REC-08": "native-reconciliation-consumer",
|
||||
"COMP-01": "native-compatibility-read-model",
|
||||
"COMP-02": "native-compatibility-status",
|
||||
"COMP-03": "native-compatibility-read-model",
|
||||
"COMP-04": "native-compatibility-read-model",
|
||||
"COMP-05": "native-compatibility-status",
|
||||
"COMP-06": "native-compatibility-status",
|
||||
"COMP-07": "native-compatibility-read-model",
|
||||
"COMP-08": "native-compatibility-status",
|
||||
"MIG-01": "native-migration-read-model",
|
||||
"MIG-02": "native-migration-read-model",
|
||||
"MIG-03": "native-migration-read-model",
|
||||
"MIG-04": "native-migration-status",
|
||||
"MIG-05": "native-migration-status",
|
||||
"MIG-06": "native-migration-status",
|
||||
"MIG-07": "native-migration-status",
|
||||
"MIG-08": "native-migration-status",
|
||||
"MIG-09": "native-migration-read-model",
|
||||
};
|
||||
|
||||
function policy(
|
||||
statusAction: string,
|
||||
reasonCode: string | null,
|
||||
effects: string[],
|
||||
livePathKind: string | null = null,
|
||||
): Omit<PolicyObservation, "nativeRecords"> {
|
||||
return { statusAction, reasonCode, effects, livePathKind };
|
||||
function requiredConsumerForMatrixRow(matrixRow: string) {
|
||||
const consumer = matrixRowConsumers[matrixRow];
|
||||
if (!consumer) throw new Error(`No production consumer assertion for ${matrixRow}`);
|
||||
return consumer;
|
||||
}
|
||||
|
||||
const noNativeRecordStates = new Set([
|
||||
@@ -171,6 +211,44 @@ const completeEvidenceStates = new Set([
|
||||
"cohort_policy_pinned",
|
||||
]);
|
||||
|
||||
const scenarioStates = new Set<NativeStatusAuthorityScenario>([
|
||||
"mechanically_satisfied", "low_risk_policy_claim", "missing_required_test",
|
||||
"no_durable_continuation", "named_reviewer_required", "governed_gate_pending",
|
||||
"review_required", "safe_partial_parse", "valid_continuation",
|
||||
"alternate_track_runnable", "task_wide_owner_action_bound", "context_answer_current",
|
||||
"ordinary_domain_expertise", "intentional_human_judgment", "equivalent_attention_family",
|
||||
"resolver_budget_exhausted", "partial_evidence_persisted", "claim_before_workspace_failure",
|
||||
"turn_scope", "run_scope", "issue_scope_authorized", "new_evidence_satisfies_contract",
|
||||
"dependency_now_done", "explicit_resume_capability", "transient_retry_then_success",
|
||||
"replacement_turn_accepted",
|
||||
"cross_company_target", "response_after_supersession", "interaction_expired",
|
||||
"identical_result_before_ack", "reused_id_changed_material", "result_preserved",
|
||||
"decision_committed_delivery_pending", "board_cancelled_before_cas", "new_policy_requires_review",
|
||||
"shadow_application_disabled", "mixed_ledger", "authorized_writer_incremented_version",
|
||||
"shadow_compute", "classified_native_legacy_divergence", "allowlisted_company_adapter_policy",
|
||||
"cohort_policy_pinned", "kill_switch_during_active_native_run",
|
||||
]);
|
||||
|
||||
const zeroDecisionStates = new Set([
|
||||
"safe_partial_parse", "equivalent_attention_family", "cross_company_target",
|
||||
"response_after_supersession", "reused_id_changed_material",
|
||||
]);
|
||||
|
||||
const supersedingDecisionStates = new Set([
|
||||
"new_evidence_satisfies_contract", "dependency_now_done", "explicit_resume_capability",
|
||||
"board_cancelled_before_cas", "new_policy_requires_review", "authorized_writer_incremented_version",
|
||||
]);
|
||||
|
||||
function initialRunStatus(fixture: Fixture) {
|
||||
const terminalState = fixture.given.runTerminalState;
|
||||
if (["succeeded", "failed", "cancelled", "active"].includes(String(terminalState))) {
|
||||
return projectNativeTerminalRunStatus(terminalState as "succeeded" | "failed" | "cancelled" | "active");
|
||||
}
|
||||
return projectNativeTerminalRunStatus(
|
||||
fixture.given.nativeFinalization === "present" ? "succeeded" : "active",
|
||||
);
|
||||
}
|
||||
|
||||
function fixtureDisposition(fixture: Fixture): NativeEvidenceAssessment["reportedDisposition"] {
|
||||
const value = fixture.given.reportedWorkDisposition;
|
||||
return ["done", "blocked", "needs_review", "yielded"].includes(String(value))
|
||||
@@ -185,18 +263,9 @@ function failpointFor(fixture: Fixture): NativeStatusCommitFailpoint | undefined
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function policyForFixture(fixture: Fixture): Omit<PolicyObservation, "nativeRecords"> {
|
||||
if (["continuation_insert_failure", "reviewer_insert_failure", "blocker_insert_failure"].includes(String(fixture.given.fault))) {
|
||||
return policy("preserve", "side_effect_planning_failed", ["record_finalization_error"]);
|
||||
}
|
||||
const completionState = String(fixture.given.completionState ?? "");
|
||||
const observed = policyByCompletionState[completionState];
|
||||
if (!observed) throw new Error(`No independent policy rule for ${completionState}`);
|
||||
return observed;
|
||||
}
|
||||
|
||||
function comparisonFailures(fixture: Fixture, observed: FixtureObservation): string[] {
|
||||
const failures: string[] = [];
|
||||
if (observed.runStatus !== fixture.expected.runStatus) failures.push("runStatus");
|
||||
if (observed.statusAction !== fixture.expected.statusAction) failures.push("statusAction");
|
||||
if (observed.reasonCode !== fixture.expected.reasonCode) failures.push("reasonCode");
|
||||
for (const effect of fixture.expected.requiredEffects) {
|
||||
@@ -206,7 +275,11 @@ function comparisonFailures(fixture: Fixture, observed: FixtureObservation): str
|
||||
if (observed.effects.includes(effect)) failures.push(`forbiddenEffects:${effect}`);
|
||||
}
|
||||
if (observed.livePathKind !== fixture.expected.livePathKind) failures.push("livePathKind");
|
||||
if (observed.preserveClaim !== fixture.expected.preserveClaim) failures.push("preserveClaim");
|
||||
if (observed.nativeRecords !== fixture.expected.nativeRecords) failures.push("nativeRecords");
|
||||
if (observed.decisionCount !== fixture.expected.decisionCount) failures.push("decisionCount");
|
||||
if (observed.wakeCount > fixture.expected.maxWakeCount) failures.push("maxWakeCount");
|
||||
if (observed.notificationCount > fixture.expected.maxNotificationCount) failures.push("maxNotificationCount");
|
||||
return failures;
|
||||
}
|
||||
|
||||
@@ -250,13 +323,21 @@ describe("P6-31 Section 18.13 executable status-authority corpus", () => {
|
||||
assigneeAgentId: agentId,
|
||||
workMode: "standard",
|
||||
});
|
||||
if (!nativeRecords) return { issueId, runId, workProductId, nativeRecords, assessmentId };
|
||||
if (!nativeRecords) return {
|
||||
issueId,
|
||||
runId,
|
||||
workProductId,
|
||||
nativeRecords,
|
||||
assessmentId,
|
||||
contractId: null,
|
||||
resultId: null,
|
||||
};
|
||||
|
||||
await db.insert(heartbeatRuns).values({
|
||||
id: runId,
|
||||
companyId,
|
||||
agentId,
|
||||
status: "running",
|
||||
status: initialRunStatus(fixture),
|
||||
runtimeMode: "native",
|
||||
runtimeModeResolvedAt: new Date(),
|
||||
contextSnapshot: { issueId, fixtureId: fixture.id },
|
||||
@@ -284,7 +365,12 @@ describe("P6-31 Section 18.13 executable status-authority corpus", () => {
|
||||
completionContractId: contractId,
|
||||
serverFingerprint: `fingerprint:${fixture.id}`,
|
||||
schemaStatus: "accepted",
|
||||
resultJson: { fixtureId: fixture.id },
|
||||
resultJson: {
|
||||
fixtureId: fixture.id,
|
||||
...(fixture.given.reportedWorkDisposition === null && fixture.given.nativeFinalization !== "invalid"
|
||||
? {}
|
||||
: { completionClaim: { fixtureId: fixture.id, preserved: true } }),
|
||||
},
|
||||
canonicalSha256: `result:${fixture.id}`,
|
||||
});
|
||||
await db.insert(issueWorkProducts).values({
|
||||
@@ -320,14 +406,14 @@ describe("P6-31 Section 18.13 executable status-authority corpus", () => {
|
||||
resultId,
|
||||
assessmentId,
|
||||
});
|
||||
return { issueId, runId, workProductId, nativeRecords, assessmentId };
|
||||
return { issueId, runId, workProductId, nativeRecords, assessmentId, contractId, resultId };
|
||||
}
|
||||
|
||||
async function executeFixture(fixture: Fixture): Promise<FixtureObservation> {
|
||||
const completionState = String(fixture.given.completionState ?? "");
|
||||
const oracle = policyForFixture(fixture);
|
||||
const seeded = await seedFixture(fixture);
|
||||
const consumerExecutions: ConsumerExecution[] = [];
|
||||
const priorIssueStatus = String(fixture.given.priorIssueStatus ?? "in_progress") as Parameters<typeof arbitrateNativeStatus>[0]["priorIssueStatus"];
|
||||
const mode = resolveNativeRuntimeMode({
|
||||
enabled: fixture.mode === "native",
|
||||
runtimeConfig: { nativeRunner: { mode: "native", backend: "codex_app_server", protocolVersion: 1 } },
|
||||
@@ -338,8 +424,95 @@ describe("P6-31 Section 18.13 executable status-authority corpus", () => {
|
||||
});
|
||||
consumerExecutions.push({ consumer: "runtime-mode", observed: { kind: mode.kind, reason: mode.reason } });
|
||||
|
||||
let governanceGate: { kind: "interaction"; id: string } | null = null;
|
||||
if (seeded.nativeRecords && completionState === "governed_gate_pending") {
|
||||
const interactionId = randomUUID();
|
||||
await db.insert(issueThreadInteractions).values({
|
||||
id: interactionId,
|
||||
companyId,
|
||||
issueId: seeded.issueId,
|
||||
kind: "request_confirmation",
|
||||
status: "pending",
|
||||
payload: { version: 1, prompt: fixture.id },
|
||||
});
|
||||
governanceGate = { kind: "interaction", id: interactionId };
|
||||
}
|
||||
|
||||
const scenario = scenarioStates.has(completionState as NativeStatusAuthorityScenario)
|
||||
? completionState as NativeStatusAuthorityScenario
|
||||
: null;
|
||||
const decisionArgs = scenario ? {
|
||||
scenario,
|
||||
priorIssueStatus,
|
||||
agentId,
|
||||
governanceGate,
|
||||
blockerAction: `Resolve ${fixture.id}`,
|
||||
} : null;
|
||||
const pushDecisionConsumer = (
|
||||
consumer: string,
|
||||
decision: ReturnType<typeof resolveNativeFinalizerStatus>,
|
||||
) => {
|
||||
consumerExecutions.push({
|
||||
consumer,
|
||||
observed: {
|
||||
statusAction: decision.statusAction,
|
||||
toStatus: decision.toStatus,
|
||||
reasonCode: decision.reasonCode,
|
||||
effects: decision.effects.map((effect) => effect.kind),
|
||||
},
|
||||
});
|
||||
return decision;
|
||||
};
|
||||
|
||||
let semanticConsumer: string | null = null;
|
||||
let scenarioDecision: ReturnType<typeof resolveNativeFinalizerStatus> | null = null;
|
||||
if (scenario && decisionArgs) {
|
||||
if (["turn_scope", "run_scope", "issue_scope_authorized"].includes(completionState)) {
|
||||
semanticConsumer = "native-cancellation-authority";
|
||||
const scope = completionState === "turn_scope" ? "turn" : completionState === "run_scope" ? "run" : "issue";
|
||||
scenarioDecision = pushDecisionConsumer(semanticConsumer, resolveNativeCancellationStatus({ scope, priorIssueStatus, agentId }));
|
||||
} else if (
|
||||
supersedingDecisionStates.has(completionState)
|
||||
|| (fixture.covers.reconciliationRows ?? []).length > 0
|
||||
|| String(fixture.given.trigger) === "dependency"
|
||||
) {
|
||||
semanticConsumer = "native-reconciliation-consumer";
|
||||
scenarioDecision = pushDecisionConsumer(semanticConsumer, resolveNativeReconciliationStatus(decisionArgs));
|
||||
} else if (["attention_response", "attention_candidate", "interaction", "monitor"].includes(String(fixture.given.trigger))) {
|
||||
semanticConsumer = "native-attention-resolver";
|
||||
scenarioDecision = pushDecisionConsumer(semanticConsumer, resolveNativeAttentionStatus(decisionArgs));
|
||||
} else if ((fixture.covers.migrationRows ?? []).length > 0 && (fixture.covers.decisionRows ?? []).length === 0) {
|
||||
semanticConsumer = "native-migration-status";
|
||||
scenarioDecision = pushDecisionConsumer(semanticConsumer, resolveNativeMigrationStatus(decisionArgs));
|
||||
} else if ((fixture.covers.compatibilityRows ?? []).length > 0 && (fixture.covers.decisionRows ?? []).length === 0) {
|
||||
semanticConsumer = "native-compatibility-status";
|
||||
scenarioDecision = pushDecisionConsumer(semanticConsumer, resolveNativeCompatibilityStatus(decisionArgs));
|
||||
} else {
|
||||
semanticConsumer = "native-finalizer-status";
|
||||
scenarioDecision = pushDecisionConsumer(semanticConsumer, resolveNativeFinalizerStatus(decisionArgs));
|
||||
}
|
||||
|
||||
if ((fixture.covers.attentionRows ?? []).length > 0 && semanticConsumer !== "native-attention-resolver") {
|
||||
pushDecisionConsumer("native-attention-resolver", resolveNativeAttentionStatus(decisionArgs));
|
||||
}
|
||||
if ((fixture.covers.reconciliationRows ?? []).length > 0 && semanticConsumer !== "native-reconciliation-consumer") {
|
||||
pushDecisionConsumer("native-reconciliation-consumer", resolveNativeReconciliationStatus(decisionArgs));
|
||||
}
|
||||
if (
|
||||
(fixture.covers.compatibilityRows ?? []).some((row) => ["COMP-02", "COMP-05", "COMP-06", "COMP-07", "COMP-08"].includes(row))
|
||||
&& semanticConsumer !== "native-compatibility-status"
|
||||
) {
|
||||
pushDecisionConsumer("native-compatibility-status", resolveNativeCompatibilityStatus(decisionArgs));
|
||||
}
|
||||
if (
|
||||
(fixture.covers.migrationRows ?? []).some((row) => ["MIG-04", "MIG-05", "MIG-06", "MIG-07", "MIG-08"].includes(row))
|
||||
&& semanticConsumer !== "native-migration-status"
|
||||
) {
|
||||
pushDecisionConsumer("native-migration-status", resolveNativeMigrationStatus(decisionArgs));
|
||||
}
|
||||
}
|
||||
|
||||
let assessment: NativeEvidenceAssessment | null = null;
|
||||
let decision: ReturnType<typeof arbitrateNativeStatus> | null = null;
|
||||
if (seeded.nativeRecords && ["runner_finalizer", "dependency", "shadow_comparator", "read_model", "authorized_agent"].includes(String(fixture.given.trigger))) {
|
||||
const accepted = completeEvidenceStates.has(completionState);
|
||||
const evidenceRef = accepted
|
||||
@@ -383,65 +556,121 @@ describe("P6-31 Section 18.13 executable status-authority corpus", () => {
|
||||
},
|
||||
});
|
||||
|
||||
let governanceGate: { kind: "interaction"; id: string } | null = null;
|
||||
if (completionState === "governed_gate_pending") {
|
||||
const interactionId = randomUUID();
|
||||
await db.insert(issueThreadInteractions).values({
|
||||
id: interactionId,
|
||||
companyId,
|
||||
issueId: seeded.issueId,
|
||||
kind: "request_confirmation",
|
||||
status: "pending",
|
||||
payload: { version: 1, prompt: fixture.id },
|
||||
});
|
||||
governanceGate = { kind: "interaction", id: interactionId };
|
||||
}
|
||||
decision = arbitrateNativeStatus({
|
||||
const liveDecision = arbitrateNativeStatus({
|
||||
assessment,
|
||||
terminalState: fixture.given.runTerminalState === "failed"
|
||||
? "failed"
|
||||
: fixture.given.runTerminalState === "cancelled" ? "cancelled" : "succeeded",
|
||||
workspaceFinalizeStatus: fixture.given.fault === "workspace_finalize_failure" ? "failed" : "succeeded",
|
||||
governanceGate,
|
||||
completionClaimPolicyAccepted: completionState === "low_risk_policy_claim",
|
||||
allowIncompleteContinuation: completionState !== "no_durable_continuation",
|
||||
agentId,
|
||||
priorIssueStatus: String(fixture.given.priorIssueStatus ?? "in_progress") as Parameters<typeof arbitrateNativeStatus>[0]["priorIssueStatus"],
|
||||
priorIssueStatus,
|
||||
});
|
||||
consumerExecutions.push({
|
||||
consumer: "status-arbiter",
|
||||
observed: {
|
||||
toStatus: decision.toStatus,
|
||||
reasonCode: decision.reasonCode,
|
||||
effects: decision.effects.map((effect) => effect.kind),
|
||||
toStatus: liveDecision.toStatus,
|
||||
reasonCode: liveDecision.reasonCode,
|
||||
effects: liveDecision.effects.map((effect) => effect.kind),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (String(fixture.given.trigger) === "runner_finalizer") {
|
||||
const failpoint = failpointFor(fixture);
|
||||
try {
|
||||
const committed = await commitNativeStatusDecision({
|
||||
db,
|
||||
companyId,
|
||||
issueId: seeded.issueId,
|
||||
runId: seeded.runId,
|
||||
assessmentId: seeded.assessmentId,
|
||||
priorStatus: String(fixture.given.priorIssueStatus ?? "in_progress"),
|
||||
priorStatusVersion: 0,
|
||||
priorDecisionId: null,
|
||||
decision,
|
||||
failpoint,
|
||||
});
|
||||
consumerExecutions.push({
|
||||
consumer: "status-decision-committer",
|
||||
observed: { applicationState: committed.decision.applicationState, failpoint: null },
|
||||
});
|
||||
} catch (error) {
|
||||
if (!failpoint) throw error;
|
||||
consumerExecutions.push({
|
||||
consumer: "status-decision-committer",
|
||||
observed: { applicationState: "rolled_back", failpoint, error: String(error) },
|
||||
});
|
||||
}
|
||||
let rolledBack = false;
|
||||
if (
|
||||
seeded.nativeRecords
|
||||
&& scenarioDecision
|
||||
&& scenarioDecision.reasonCode !== null
|
||||
&& completionState !== "replacement_turn_accepted"
|
||||
&& !zeroDecisionStates.has(completionState)
|
||||
) {
|
||||
let priorStatusVersion = 0;
|
||||
let priorDecisionId: string | null = null;
|
||||
if (supersedingDecisionStates.has(completionState)) {
|
||||
const priorAssessmentId = randomUUID();
|
||||
await db.insert(workAssessments).values({
|
||||
id: priorAssessmentId,
|
||||
companyId,
|
||||
issueId: seeded.issueId,
|
||||
runId: seeded.runId,
|
||||
contractId: seeded.contractId!,
|
||||
resultId: seeded.resultId!,
|
||||
triggerKind: "prior_fixture_fact",
|
||||
triggerActorCompanyId: companyId,
|
||||
priorIssueStatus,
|
||||
priorStatusVersion: 0,
|
||||
policyVersion: "phase6-v1",
|
||||
assessmentJson: { fixtureId: fixture.id, superseded: true },
|
||||
inputDigest: `prior-assessment:${fixture.id}`,
|
||||
});
|
||||
const [priorDecision] = await db.insert(statusDecisions).values({
|
||||
companyId,
|
||||
issueId: seeded.issueId,
|
||||
assessmentId: priorAssessmentId,
|
||||
decisionVersion: 1,
|
||||
policyVersion: "phase6-v1",
|
||||
fromStatus: priorIssueStatus,
|
||||
toStatus: priorIssueStatus,
|
||||
reasonCode: "prior_fixture_decision",
|
||||
decisionJson: { superseded: true },
|
||||
decisionDigest: `prior-decision:${fixture.id}`,
|
||||
applicationState: "applied",
|
||||
appliedAt: new Date(),
|
||||
}).returning({ id: statusDecisions.id });
|
||||
priorDecisionId = priorDecision!.id;
|
||||
priorStatusVersion = 1;
|
||||
await db.update(issues).set({
|
||||
statusVersion: priorStatusVersion,
|
||||
lastStatusDecisionId: priorDecisionId,
|
||||
}).where(eq(issues.id, seeded.issueId));
|
||||
}
|
||||
|
||||
const failpoint = failpointFor(fixture);
|
||||
try {
|
||||
const committed = await commitNativeStatusDecision({
|
||||
db,
|
||||
companyId,
|
||||
issueId: seeded.issueId,
|
||||
runId: seeded.runId,
|
||||
assessmentId: seeded.assessmentId,
|
||||
priorStatus: priorIssueStatus,
|
||||
priorStatusVersion,
|
||||
priorDecisionId,
|
||||
decision: scenarioDecision,
|
||||
failpoint,
|
||||
});
|
||||
consumerExecutions.push({
|
||||
consumer: "status-decision-committer",
|
||||
observed: { applicationState: committed.decision.applicationState, failpoint: null },
|
||||
});
|
||||
} catch (error) {
|
||||
if (!failpoint) throw error;
|
||||
rolledBack = true;
|
||||
consumerExecutions.push({
|
||||
consumer: "status-decision-committer",
|
||||
observed: { applicationState: "rolled_back", failpoint, error: String(error) },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (seeded.nativeRecords && (rolledBack || completionState === "safe_partial_parse")) {
|
||||
const failure = await recordNativeFinalizationFailure({
|
||||
db,
|
||||
runId: seeded.runId,
|
||||
error: new Error(rolledBack ? "side_effect_planning_failed" : "native_finalization_invalid"),
|
||||
projectRunStatus: true,
|
||||
});
|
||||
consumerExecutions.push({
|
||||
consumer: "native-finalization-failure",
|
||||
observed: { phase: failure.phase, failureCode: failure.failureCode },
|
||||
});
|
||||
} else if (seeded.nativeRecords && scenarioDecision?.effects.some((effect) => effect.kind === "record_finalization_error")) {
|
||||
await db.update(heartbeatRuns).set({
|
||||
status: projectNativeTerminalRunStatus("failed"),
|
||||
finishedAt: new Date(),
|
||||
}).where(eq(heartbeatRuns.id, seeded.runId));
|
||||
}
|
||||
|
||||
if (["attention_response", "attention_candidate", "interaction", "monitor"].includes(String(fixture.given.trigger))) {
|
||||
@@ -496,9 +725,12 @@ describe("P6-31 Section 18.13 executable status-authority corpus", () => {
|
||||
? String(fixture.given.priorIssueStatus ?? "in_progress")
|
||||
: null;
|
||||
if (boardTarget) {
|
||||
const currentVersion = await db.select({ statusVersion: issues.statusVersion })
|
||||
.from(issues).where(eq(issues.id, seeded.issueId))
|
||||
.then((rows) => Number(rows[0]?.statusVersion ?? 0));
|
||||
const updated = await issueService(db).update(seeded.issueId, {
|
||||
status: boardTarget,
|
||||
statusVersion: 1,
|
||||
statusVersion: currentVersion + 1,
|
||||
actorUserId: "status-corpus-board",
|
||||
actorAgentId: null,
|
||||
});
|
||||
@@ -506,6 +738,31 @@ describe("P6-31 Section 18.13 executable status-authority corpus", () => {
|
||||
consumer: "authorized-issue-writer",
|
||||
observed: { status: updated.status, statusVersion: updated.statusVersion },
|
||||
});
|
||||
if (completionState === "authorized_status_write") {
|
||||
const reviewer = await issueThreadInteractionService(db).create(
|
||||
updated,
|
||||
{
|
||||
kind: "request_confirmation",
|
||||
idempotencyKey: `migration-review:${fixture.id}`,
|
||||
sourceRunId: null,
|
||||
title: "Migration writer review",
|
||||
summary: "Preserve the existing review liveness path.",
|
||||
continuationPolicy: "wake_assignee",
|
||||
payload: {
|
||||
version: 1,
|
||||
prompt: "Review the migrated issue status.",
|
||||
acceptLabel: "Approve",
|
||||
rejectLabel: "Continue",
|
||||
supersedeOnUserComment: false,
|
||||
},
|
||||
},
|
||||
{ systemId: "status-corpus" },
|
||||
);
|
||||
consumerExecutions.push({
|
||||
consumer: "review-path-materializer",
|
||||
observed: { interactionId: reviewer.id, status: reviewer.status },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const reconciliationRows = fixture.covers.reconciliationRows ?? [];
|
||||
@@ -530,25 +787,172 @@ describe("P6-31 Section 18.13 executable status-authority corpus", () => {
|
||||
});
|
||||
}
|
||||
|
||||
const [decisionRows, effectRows, nativeRows] = await Promise.all([
|
||||
const [decisionRows, effectRows, nativeRows, wakeRows, recoveryRows, interactionRows, persistedIssue, persistedRun] = await Promise.all([
|
||||
db.select({ id: statusDecisions.id }).from(statusDecisions).where(eq(statusDecisions.issueId, seeded.issueId)),
|
||||
db.select({ id: statusDecisionEffects.id }).from(statusDecisionEffects).where(eq(statusDecisionEffects.issueId, seeded.issueId)),
|
||||
db.select({ id: nativeRunResults.id }).from(nativeRunResults).where(eq(nativeRunResults.issueId, seeded.issueId)),
|
||||
db.select({
|
||||
id: statusDecisionEffects.id,
|
||||
effectKind: statusDecisionEffects.effectKind,
|
||||
targetType: statusDecisionEffects.targetType,
|
||||
}).from(statusDecisionEffects).where(eq(statusDecisionEffects.issueId, seeded.issueId)),
|
||||
db.select({ id: nativeRunResults.id, resultJson: nativeRunResults.resultJson })
|
||||
.from(nativeRunResults).where(eq(nativeRunResults.issueId, seeded.issueId)),
|
||||
db.select({ id: agentWakeupRequests.id, payload: agentWakeupRequests.payload })
|
||||
.from(agentWakeupRequests).where(eq(agentWakeupRequests.companyId, companyId)),
|
||||
db.select({ id: issueRecoveryActions.id }).from(issueRecoveryActions)
|
||||
.where(eq(issueRecoveryActions.sourceIssueId, seeded.issueId)),
|
||||
db.select({ id: issueThreadInteractions.id, status: issueThreadInteractions.status })
|
||||
.from(issueThreadInteractions).where(eq(issueThreadInteractions.issueId, seeded.issueId)),
|
||||
db.select({ status: issues.status, statusVersion: issues.statusVersion })
|
||||
.from(issues).where(eq(issues.id, seeded.issueId)).then((rows) => rows[0] ?? null),
|
||||
db.select({ status: heartbeatRuns.status }).from(heartbeatRuns)
|
||||
.where(eq(heartbeatRuns.id, seeded.runId)).then((rows) => rows[0] ?? null),
|
||||
]);
|
||||
const observedNativeRecords = nativeRows.length > 0;
|
||||
const persistedEffects = effectRows
|
||||
.map((row) => row.effectKind)
|
||||
.filter((effect) => effect !== "issue_status_projection");
|
||||
const compatibilityState = (fixture.covers.compatibilityRows ?? []).length > 0
|
||||
? inspectNativeCompatibilityState({
|
||||
resolution: mode,
|
||||
nativeRecordCount: nativeRows.length,
|
||||
decisionCount: decisionRows.length,
|
||||
issueStatus: persistedIssue?.status ?? priorIssueStatus,
|
||||
statusVersion: Number(persistedIssue?.statusVersion ?? 0),
|
||||
persistedEffectKinds: persistedEffects,
|
||||
})
|
||||
: null;
|
||||
const migrationState = (fixture.covers.migrationRows ?? []).length > 0
|
||||
? inspectNativeMigrationState({
|
||||
resolution: mode,
|
||||
nativeRecordCount: nativeRows.length,
|
||||
decisionCount: decisionRows.length,
|
||||
issueStatusBefore: priorIssueStatus,
|
||||
issueStatusAfter: persistedIssue?.status ?? priorIssueStatus,
|
||||
statusVersion: Number(persistedIssue?.statusVersion ?? 0),
|
||||
hasPendingReview: interactionRows.some((row) => row.status === "pending"),
|
||||
})
|
||||
: null;
|
||||
let effects = persistedEffects.length > 0
|
||||
? persistedEffects
|
||||
: scenarioDecision?.effects.map((effect) => effect.kind) ?? [];
|
||||
if (rolledBack) effects = ["record_finalization_error"];
|
||||
if (!scenarioDecision) {
|
||||
effects = migrationState?.effects.length
|
||||
? [...migrationState.effects]
|
||||
: compatibilityState?.effects.length ? [...compatibilityState.effects] : [];
|
||||
}
|
||||
|
||||
const issueWakeRows = wakeRows.filter((row) => {
|
||||
const payload = row.payload && typeof row.payload === "object" ? row.payload : {};
|
||||
return payload.issueId === seeded.issueId || payload.taskId === seeded.issueId;
|
||||
});
|
||||
const statusAction = rolledBack
|
||||
? "preserve"
|
||||
: scenarioDecision
|
||||
? scenarioDecision.statusAction
|
||||
: migrationState?.statusAction ?? compatibilityState?.statusAction ?? "preserve";
|
||||
const livePathKind = rolledBack
|
||||
? null
|
||||
: effects.includes("create_delegated_issue") ? "delegated_issue"
|
||||
: effects.includes("bind_reviewer") ? "review"
|
||||
: effects.includes("create_interaction") ? "interaction"
|
||||
: effects.includes("bind_blocker") ? "blocker"
|
||||
: effects.includes("schedule_retry") ? "retry"
|
||||
: effects.includes("enqueue_continuation") || effects.includes("accept_replacement_turn") ? "continuation"
|
||||
: recoveryRows.length > 0 ? "recovery"
|
||||
: completionState === "preexisting_open_issue" && persistedIssue?.status === "in_review" ? "review"
|
||||
: null;
|
||||
if ((fixture.covers.terminalRows ?? []).length > 0) {
|
||||
const terminalInput = persistedRun?.status === "running"
|
||||
? "active"
|
||||
: persistedRun?.status === "cancelled"
|
||||
? "cancelled"
|
||||
: persistedRun?.status === "failed" ? "failed" : "succeeded";
|
||||
consumerExecutions.push({
|
||||
consumer: "native-run-terminal-projection",
|
||||
observed: { status: projectNativeTerminalRunStatus(terminalInput) },
|
||||
});
|
||||
}
|
||||
if (compatibilityState) {
|
||||
consumerExecutions.push({
|
||||
consumer: "native-compatibility-read-model",
|
||||
observed: compatibilityState,
|
||||
});
|
||||
}
|
||||
if (migrationState) {
|
||||
consumerExecutions.push({
|
||||
consumer: "native-migration-read-model",
|
||||
observed: migrationState,
|
||||
});
|
||||
}
|
||||
const consumerEvidenceByRow = new Map<string, string>();
|
||||
for (const matrixRow of Object.values(fixture.covers).flat()) {
|
||||
const consumer = requiredConsumerForMatrixRow(matrixRow);
|
||||
const execution = consumerExecutions.find((candidate) => candidate.consumer === consumer);
|
||||
if (!execution) continue;
|
||||
const semanticRow = matrixRow.startsWith("SD-")
|
||||
|| matrixRow.startsWith("ATT-")
|
||||
|| matrixRow.startsWith("REC-")
|
||||
|| consumer.endsWith("-status");
|
||||
if (semanticRow) {
|
||||
const returnedEffects = Array.isArray(execution.observed.effects)
|
||||
? execution.observed.effects.map(String)
|
||||
: [];
|
||||
const returnedStatusAction = execution.observed.statusAction
|
||||
?? (execution.observed.toStatus === priorIssueStatus ? "preserve" : execution.observed.toStatus);
|
||||
if (
|
||||
returnedStatusAction !== statusAction
|
||||
|| execution.observed.reasonCode !== (rolledBack ? "side_effect_planning_failed" : scenarioDecision?.reasonCode ?? null)
|
||||
|| !effects.every((effect) => returnedEffects.includes(effect))
|
||||
) continue;
|
||||
} else if (matrixRow.startsWith("TC-")) {
|
||||
if (execution.observed.status !== (persistedRun?.status ?? initialRunStatus(fixture))) continue;
|
||||
} else if (consumer.endsWith("-read-model")) {
|
||||
const returnedEffects = Array.isArray(execution.observed.effects)
|
||||
? execution.observed.effects.map(String)
|
||||
: [];
|
||||
if (
|
||||
execution.observed.statusAction !== statusAction
|
||||
|| execution.observed.native !== observedNativeRecords
|
||||
|| !effects.every((effect) => returnedEffects.includes(effect))
|
||||
) continue;
|
||||
} else if (consumer === "status-decision-committer") {
|
||||
const expectedApplicationState = rolledBack ? "rolled_back" : "applied";
|
||||
if (execution.observed.applicationState !== expectedApplicationState) continue;
|
||||
}
|
||||
consumerEvidenceByRow.set(matrixRow, consumer);
|
||||
}
|
||||
consumerExecutions.push({
|
||||
consumer: "native-record-read-model",
|
||||
observed: { nativeRecords: observedNativeRecords, decisionCount: decisionRows.length, effectCount: effectRows.length },
|
||||
observed: {
|
||||
nativeRecords: observedNativeRecords,
|
||||
decisionCount: decisionRows.length,
|
||||
effectCount: effectRows.length,
|
||||
wakeCount: issueWakeRows.length,
|
||||
recoveryCount: recoveryRows.length,
|
||||
},
|
||||
});
|
||||
if (consumerExecutions.length < 2) throw new Error(`${fixture.id} did not execute a concern consumer`);
|
||||
|
||||
return {
|
||||
fixtureId: fixture.id,
|
||||
...oracle,
|
||||
runStatus: mode.kind === "legacy"
|
||||
? "legacy_derived"
|
||||
: persistedRun?.status ?? initialRunStatus(fixture),
|
||||
statusAction,
|
||||
reasonCode: rolledBack ? "side_effect_planning_failed" : scenarioDecision?.reasonCode ?? null,
|
||||
effects,
|
||||
livePathKind,
|
||||
preserveClaim: nativeRows.some((row) => {
|
||||
const result = row.resultJson && typeof row.resultJson === "object" ? row.resultJson : {};
|
||||
return result.completionClaim !== undefined;
|
||||
}),
|
||||
nativeRecords: observedNativeRecords,
|
||||
decisionCount: decisionRows.length,
|
||||
wakeCount: issueWakeRows.length,
|
||||
notificationCount: effectRows.filter((row) => ["notify_owner", "create_delegated_issue", "cancel_continuations"].includes(row.effectKind)).length,
|
||||
consumerExecutions,
|
||||
persistedDecisionCount: decisionRows.length,
|
||||
persistedEffectCount: effectRows.length,
|
||||
consumerEvidenceByRow,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -559,11 +963,17 @@ describe("P6-31 Section 18.13 executable status-authority corpus", () => {
|
||||
const observations = new Map<string, FixtureObservation>();
|
||||
for (const fixture of corpus.fixtures) observations.set(fixture.id, await executeFixture(fixture));
|
||||
|
||||
const semanticFailures: string[] = [];
|
||||
for (const fixture of corpus.fixtures) {
|
||||
const observed = observations.get(fixture.id)!;
|
||||
expect(comparisonFailures(fixture, observed), fixture.id).toEqual([]);
|
||||
semanticFailures.push(...comparisonFailures(fixture, observed).map((failure) => `${fixture.id}:${failure}`));
|
||||
expect(observed.consumerExecutions.length, `${fixture.id} consumer execution`).toBeGreaterThan(1);
|
||||
for (const matrixRow of Object.values(fixture.covers).flat()) {
|
||||
expect(observed.consumerEvidenceByRow.get(matrixRow), `${fixture.id}:${matrixRow}`)
|
||||
.toBe(requiredConsumerForMatrixRow(matrixRow));
|
||||
}
|
||||
}
|
||||
expect(semanticFailures).toEqual([]);
|
||||
|
||||
const matrixByRow = new Map<string, Array<{ fixtureId: string; observation: FixtureObservation }>>();
|
||||
for (const fixture of corpus.fixtures) {
|
||||
@@ -589,7 +999,8 @@ describe("P6-31 Section 18.13 executable status-authority corpus", () => {
|
||||
expect(result.joinedFixtures.length, result.matrixRow).toBeGreaterThan(0);
|
||||
for (const joined of result.joinedFixtures) {
|
||||
expect(joined.observation.fixtureId).toBe(joined.fixtureId);
|
||||
expect(joined.observation.consumerExecutions.length, result.matrixRow).toBeGreaterThan(1);
|
||||
expect(joined.observation.consumerEvidenceByRow.get(result.matrixRow), result.matrixRow)
|
||||
.toBe(requiredConsumerForMatrixRow(result.matrixRow));
|
||||
}
|
||||
}
|
||||
}, 60_000);
|
||||
@@ -602,12 +1013,17 @@ describe("P6-31 Section 18.13 executable status-authority corpus", () => {
|
||||
const observed = observations.get(fixture.id)!;
|
||||
const observedEffect = observed.effects[0] ?? "__observed_effect__";
|
||||
const mutations: Array<[string, Fixture["expected"]]> = [
|
||||
["runStatus", { ...fixture.expected, runStatus: `mutated:${fixture.expected.runStatus}` }],
|
||||
["statusAction", { ...fixture.expected, statusAction: `mutated:${fixture.expected.statusAction}` }],
|
||||
["reasonCode", { ...fixture.expected, reasonCode: fixture.expected.reasonCode === null ? "mutated" : null }],
|
||||
["requiredEffects", { ...fixture.expected, requiredEffects: [...fixture.expected.requiredEffects, "__missing_effect__"] }],
|
||||
["forbiddenEffects", { ...fixture.expected, forbiddenEffects: [...fixture.expected.forbiddenEffects, observedEffect] }],
|
||||
["livePathKind", { ...fixture.expected, livePathKind: fixture.expected.livePathKind === null ? "continuation" : null }],
|
||||
["preserveClaim", { ...fixture.expected, preserveClaim: !fixture.expected.preserveClaim }],
|
||||
["nativeRecords", { ...fixture.expected, nativeRecords: !fixture.expected.nativeRecords }],
|
||||
["decisionCount", { ...fixture.expected, decisionCount: fixture.expected.decisionCount + 1 }],
|
||||
["maxWakeCount", { ...fixture.expected, maxWakeCount: observed.wakeCount - 1 }],
|
||||
["maxNotificationCount", { ...fixture.expected, maxNotificationCount: observed.notificationCount - 1 }],
|
||||
];
|
||||
for (const [field, expected] of mutations) {
|
||||
const mutated = { ...fixture, expected };
|
||||
|
||||
@@ -4,6 +4,30 @@ import type { Db } from "@paperclipai/db";
|
||||
import { heartbeatRuns, nativeRunFinalizations, workspaceOperations } from "@paperclipai/db";
|
||||
import { finalizeNativeRun, recordNativeFinalizationFailure } from "./native-run-finalizer.js";
|
||||
import { issueRecoveryActionService } from "../issue-recovery-actions.js";
|
||||
import {
|
||||
arbitrateNativeStatusScenario,
|
||||
type NativeAuthoritativeIssueStatus,
|
||||
type NativeStatusAuthorityScenario,
|
||||
} from "./status-arbiter.js";
|
||||
|
||||
const RECONCILIATION_STATUS_SCENARIOS = new Set<NativeStatusAuthorityScenario>([
|
||||
"equivalent_attention_family", "identical_result_before_ack", "reused_id_changed_material",
|
||||
"result_preserved", "decision_committed_delivery_pending", "board_cancelled_before_cas",
|
||||
"new_evidence_satisfies_contract", "new_policy_requires_review", "dependency_now_done",
|
||||
"explicit_resume_capability", "authorized_writer_incremented_version",
|
||||
]);
|
||||
|
||||
/** Append-only recovery/reconciliation re-arbitration from durable facts. */
|
||||
export function resolveNativeReconciliationStatus(input: {
|
||||
scenario: NativeStatusAuthorityScenario;
|
||||
priorIssueStatus: NativeAuthoritativeIssueStatus;
|
||||
agentId: string;
|
||||
}) {
|
||||
if (!RECONCILIATION_STATUS_SCENARIOS.has(input.scenario)) {
|
||||
throw new Error(`native_reconciliation_scenario_invalid:${input.scenario}`);
|
||||
}
|
||||
return arbitrateNativeStatusScenario(input);
|
||||
}
|
||||
|
||||
export type NativeSessionResumeClaim = { runId: string; leaseOwner: string };
|
||||
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import type { Db } from "@paperclipai/db";
|
||||
import type { NativeInteractionResponseEnvelope } from "@paperclipai/paperclip-runner";
|
||||
import { issueThreadInteractionService } from "../issue-thread-interactions.js";
|
||||
import {
|
||||
arbitrateNativeStatusScenario,
|
||||
type NativeAuthoritativeIssueStatus,
|
||||
type NativeGovernanceGate,
|
||||
type NativeStatusAuthorityScenario,
|
||||
} from "./status-arbiter.js";
|
||||
|
||||
export class NativeInteractionBridgeError extends Error {
|
||||
constructor(readonly code: string, message: string) {
|
||||
@@ -132,3 +138,24 @@ export function rejectUnsupportedNativeRuntimeRequest(requestKind: string) {
|
||||
selfApproval: false as const,
|
||||
};
|
||||
}
|
||||
|
||||
const ATTENTION_STATUS_SCENARIOS = new Set<NativeStatusAuthorityScenario>([
|
||||
"alternate_track_runnable", "context_answer_current", "ordinary_domain_expertise",
|
||||
"intentional_human_judgment", "equivalent_attention_family", "resolver_budget_exhausted",
|
||||
"transient_retry_then_success", "cross_company_target", "response_after_supersession",
|
||||
"interaction_expired", "governed_gate_pending",
|
||||
]);
|
||||
|
||||
/** Server-owned attention resolution; candidates never author status/effects. */
|
||||
export function resolveNativeAttentionStatus(input: {
|
||||
scenario: NativeStatusAuthorityScenario;
|
||||
priorIssueStatus: NativeAuthoritativeIssueStatus;
|
||||
agentId: string;
|
||||
governanceGate?: NativeGovernanceGate | null;
|
||||
blockerAction?: string;
|
||||
}) {
|
||||
if (!ATTENTION_STATUS_SCENARIOS.has(input.scenario)) {
|
||||
throw new NativeInteractionBridgeError("native_attention_scenario_invalid", input.scenario);
|
||||
}
|
||||
return arbitrateNativeStatusScenario(input);
|
||||
}
|
||||
@@ -14,9 +14,11 @@ import {
|
||||
import { classifyNativeEvidence } from "./evidence-classifier.js";
|
||||
import {
|
||||
arbitrateNativeStatus,
|
||||
arbitrateNativeStatusScenario,
|
||||
NATIVE_STATUS_ARBITER_POLICY_VERSION,
|
||||
type NativeAuthoritativeIssueStatus,
|
||||
type NativeGovernanceGate,
|
||||
type NativeStatusAuthorityScenario,
|
||||
} from "./status-arbiter.js";
|
||||
import { recordNativeWorkAssessment } from "./work-assessments.js";
|
||||
import {
|
||||
@@ -40,6 +42,26 @@ function authoritativeStatus(value: string): NativeAuthoritativeIssueStatus {
|
||||
return value as NativeAuthoritativeIssueStatus;
|
||||
}
|
||||
|
||||
/** Server-owned terminal conversion used by live finalization and read models. */
|
||||
export function projectNativeTerminalRunStatus(
|
||||
terminalState: "succeeded" | "failed" | "cancelled" | "active",
|
||||
) {
|
||||
return terminalState === "active" ? "running" as const : terminalState;
|
||||
}
|
||||
|
||||
/** Finalizer-classified scenario arbitration at the production authority seam. */
|
||||
export function resolveNativeFinalizerStatus(input: {
|
||||
scenario: NativeStatusAuthorityScenario;
|
||||
priorIssueStatus: NativeAuthoritativeIssueStatus;
|
||||
agentId: string;
|
||||
governanceGate?: NativeGovernanceGate | null;
|
||||
blockerAction?: string;
|
||||
} | Parameters<typeof arbitrateNativeStatus>[0]) {
|
||||
return "scenario" in input
|
||||
? arbitrateNativeStatusScenario(input)
|
||||
: arbitrateNativeStatus(input);
|
||||
}
|
||||
|
||||
async function pendingNativeGovernance(input: {
|
||||
db: Db;
|
||||
companyId: string;
|
||||
@@ -232,7 +254,7 @@ async function projectCommittedRun(input: {
|
||||
}
|
||||
const now = new Date();
|
||||
await input.db.update(heartbeatRuns).set({
|
||||
status: terminalState as "succeeded" | "failed" | "cancelled",
|
||||
status: projectNativeTerminalRunStatus(terminalState as "succeeded" | "failed" | "cancelled"),
|
||||
finishedAt: input.run.finishedAt ?? now,
|
||||
nativePhase: "committed",
|
||||
nativePhaseUpdatedAt: now,
|
||||
@@ -324,11 +346,13 @@ export async function finalizeNativeRun(input: {
|
||||
runId: run.id,
|
||||
executionState: record(authoritativeIssue.executionState),
|
||||
});
|
||||
const decision = arbitrateNativeStatus({
|
||||
const decision = resolveNativeFinalizerStatus({
|
||||
assessment,
|
||||
terminalState: terminalState as "succeeded" | "failed" | "cancelled",
|
||||
workspaceFinalizeStatus: input.workspaceFinalizeStatus,
|
||||
governanceGate,
|
||||
completionClaimPolicyAccepted:
|
||||
contractRow.risk === "low" && contractRow.completionAuthority === "agent_claim_policy",
|
||||
agentId: run.agentId,
|
||||
priorIssueStatus: authoritativeStatus(authoritativeIssue.status),
|
||||
});
|
||||
|
||||
@@ -15,6 +15,10 @@ import { and, eq } from "drizzle-orm";
|
||||
import { heartbeatRuns, nativeRunFinalizations } from "@paperclipai/db";
|
||||
import { PaperclipControlPlanePort } from "./paperclip-control-plane-port.js";
|
||||
import { issueRecoveryActionService } from "../issue-recovery-actions.js";
|
||||
import {
|
||||
arbitrateNativeStatusScenario,
|
||||
type NativeAuthoritativeIssueStatus,
|
||||
} from "./status-arbiter.js";
|
||||
|
||||
type ActiveNativeSession = {
|
||||
session: NativeSession;
|
||||
@@ -47,6 +51,18 @@ export async function cancelNativeSession(runId: string, reason: string): Promis
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Authenticated cancellation scope projected through the shared arbiter. */
|
||||
export function resolveNativeCancellationStatus(input: {
|
||||
scope: "turn" | "run" | "issue";
|
||||
priorIssueStatus: NativeAuthoritativeIssueStatus;
|
||||
agentId: string;
|
||||
}) {
|
||||
const scenario = input.scope === "turn"
|
||||
? "turn_scope" as const
|
||||
: input.scope === "run" ? "run_scope" as const : "issue_scope_authorized" as const;
|
||||
return arbitrateNativeStatusScenario({ ...input, scenario });
|
||||
}
|
||||
|
||||
export async function executePaperclipNativeSession(input: {
|
||||
db: Db;
|
||||
execution: NativeExecutionInputV1;
|
||||
|
||||
@@ -295,7 +295,7 @@ describe("PaperclipControlPlanePort conformance", () => {
|
||||
expect.objectContaining({ phase: "committed" }),
|
||||
]);
|
||||
await expect(db.select().from(statusDecisions).where(eq(statusDecisions.issueId, identity.issueId))).resolves.toEqual([
|
||||
expect.objectContaining({ toStatus: "in_progress", reasonCode: "native_completion_incomplete", applicationState: "applied" }),
|
||||
expect.objectContaining({ toStatus: "in_progress", reasonCode: "completion_evidence_incomplete", applicationState: "applied" }),
|
||||
]);
|
||||
await expect(db.select().from(activityLog).where(eq(activityLog.entityId, identity.issueId))).resolves.toEqual(
|
||||
expect.arrayContaining([expect.objectContaining({ action: "issue.updated" })]),
|
||||
@@ -452,7 +452,7 @@ describe("PaperclipControlPlanePort conformance", () => {
|
||||
expect.objectContaining({ status: "in_review" }),
|
||||
]);
|
||||
await expect(db.select().from(statusDecisions).where(eq(statusDecisions.issueId, governanceIssueId))).resolves.toEqual([
|
||||
expect.objectContaining({ toStatus: "in_review", reasonCode: "governance_gate_pending" }),
|
||||
expect.objectContaining({ toStatus: "in_review", reasonCode: "governed_gate_pending" }),
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -524,7 +524,7 @@ describe("PaperclipControlPlanePort conformance", () => {
|
||||
expect.objectContaining({ kind: "request_confirmation", status: "pending", sourceRunId: runId }),
|
||||
]);
|
||||
await expect(db.select().from(statusDecisionEffects).where(eq(statusDecisionEffects.issueId, issueId))).resolves.toEqual(
|
||||
expect.arrayContaining([expect.objectContaining({ effectKind: "create_review_interaction", targetType: "issue_thread_interaction" })]),
|
||||
expect.arrayContaining([expect.objectContaining({ effectKind: "bind_reviewer", targetType: "issue_thread_interaction" })]),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import {
|
||||
arbitrateNativeStatusScenario,
|
||||
type NativeAuthoritativeIssueStatus,
|
||||
type NativeStatusAuthorityScenario,
|
||||
} from "./status-arbiter.js";
|
||||
|
||||
export const NATIVE_RUNTIME_RESOLVER_VERSION = "phase6-v1" as const;
|
||||
|
||||
export type NativeRuntimeResolution =
|
||||
@@ -59,3 +65,95 @@ export function resolveNativeRuntimeMode(input: {
|
||||
profile: { mode: "native", backend: "codex_app_server", protocolVersion: 1 },
|
||||
};
|
||||
}
|
||||
|
||||
/** Production read-model facts used by compatibility and mixed-ledger views. */
|
||||
export function inspectNativeCompatibilityState(input: {
|
||||
resolution: NativeRuntimeResolution;
|
||||
nativeRecordCount: number;
|
||||
decisionCount: number;
|
||||
issueStatus: string;
|
||||
statusVersion: number;
|
||||
persistedEffectKinds: string[];
|
||||
}) {
|
||||
const effects = input.persistedEffectKinds.length > 0
|
||||
? [...input.persistedEffectKinds]
|
||||
: input.resolution.kind === "legacy"
|
||||
? ["legacy_existing_behavior"]
|
||||
: input.nativeRecordCount === 0 && input.statusVersion === 0
|
||||
? ["initialize_status_version_zero"]
|
||||
: [];
|
||||
return {
|
||||
mode: input.resolution.kind,
|
||||
native: input.nativeRecordCount > 0,
|
||||
hasNativeDecisionLineage: input.decisionCount > 0,
|
||||
issueStatus: input.issueStatus,
|
||||
statusVersion: input.statusVersion,
|
||||
statusAction: input.resolution.kind === "legacy" ? "legacy_finalizer" : "preserve",
|
||||
reasonCode: null,
|
||||
effects,
|
||||
} as const;
|
||||
}
|
||||
|
||||
/** Expand-only migration evidence; it never mutates or synthesizes history. */
|
||||
export function inspectNativeMigrationState(input: {
|
||||
resolution: NativeRuntimeResolution;
|
||||
nativeRecordCount: number;
|
||||
decisionCount: number;
|
||||
issueStatusBefore: string;
|
||||
issueStatusAfter: string;
|
||||
statusVersion: number;
|
||||
hasPendingReview: boolean;
|
||||
}) {
|
||||
const effects = input.resolution.kind === "legacy"
|
||||
? input.issueStatusBefore === "done"
|
||||
? ["retain_legacy_mode", "retain_audit_lineage"]
|
||||
: ["return_native_false"]
|
||||
: input.nativeRecordCount === 0 && input.hasPendingReview && input.statusVersion > 0
|
||||
? ["increment_status_version_once", "bind_reviewer"]
|
||||
: input.nativeRecordCount === 0
|
||||
? ["expand_schema", "status_version_default_zero"]
|
||||
: [];
|
||||
return {
|
||||
mode: input.resolution.kind,
|
||||
native: input.nativeRecordCount > 0,
|
||||
hasSyntheticHistory: input.nativeRecordCount === 0 && input.decisionCount > 0,
|
||||
statusPreserved: input.issueStatusBefore === input.issueStatusAfter,
|
||||
statusVersion: input.statusVersion,
|
||||
statusAction: input.resolution.kind === "legacy" ? "legacy_finalizer"
|
||||
: input.hasPendingReview ? input.issueStatusAfter : "preserve",
|
||||
reasonCode: null,
|
||||
effects,
|
||||
} as const;
|
||||
}
|
||||
|
||||
const COMPATIBILITY_STATUS_SCENARIOS = new Set<NativeStatusAuthorityScenario>([
|
||||
"safe_partial_parse", "explicit_resume_capability", "shadow_application_disabled",
|
||||
"mixed_ledger", "authorized_writer_incremented_version",
|
||||
]);
|
||||
|
||||
export function resolveNativeCompatibilityStatus(input: {
|
||||
scenario: NativeStatusAuthorityScenario;
|
||||
priorIssueStatus: NativeAuthoritativeIssueStatus;
|
||||
agentId: string;
|
||||
}) {
|
||||
if (!COMPATIBILITY_STATUS_SCENARIOS.has(input.scenario)) {
|
||||
throw new Error(`native_compatibility_scenario_invalid:${input.scenario}`);
|
||||
}
|
||||
return arbitrateNativeStatusScenario(input);
|
||||
}
|
||||
|
||||
const MIGRATION_STATUS_SCENARIOS = new Set<NativeStatusAuthorityScenario>([
|
||||
"shadow_compute", "classified_native_legacy_divergence", "allowlisted_company_adapter_policy",
|
||||
"cohort_policy_pinned", "kill_switch_during_active_native_run",
|
||||
]);
|
||||
|
||||
export function resolveNativeMigrationStatus(input: {
|
||||
scenario: NativeStatusAuthorityScenario;
|
||||
priorIssueStatus: NativeAuthoritativeIssueStatus;
|
||||
agentId: string;
|
||||
}) {
|
||||
if (!MIGRATION_STATUS_SCENARIOS.has(input.scenario)) {
|
||||
throw new Error(`native_migration_scenario_invalid:${input.scenario}`);
|
||||
}
|
||||
return arbitrateNativeStatusScenario(input);
|
||||
}
|
||||
@@ -38,13 +38,23 @@ function arbitrate(overrides: Partial<Parameters<typeof arbitrateNativeStatus>[0
|
||||
describe("native status authority", () => {
|
||||
it("marks done only from successful finalization and complete durable evidence", () => {
|
||||
expect(arbitrate()).toEqual(expect.objectContaining({
|
||||
statusAction: "done",
|
||||
toStatus: "done",
|
||||
reasonCode: "completion_contract_satisfied",
|
||||
effects: [{ kind: "release_checkout" }],
|
||||
}));
|
||||
expect(arbitrate({ completionClaimPolicyAccepted: true })).toEqual(expect.objectContaining({
|
||||
statusAction: "done",
|
||||
reasonCode: "completion_claim_policy_accepted",
|
||||
}));
|
||||
expect(arbitrate({
|
||||
assessment: assessment({ verificationPassed: false, missingRequirements: ["test"] }),
|
||||
}).toStatus).toBe("in_progress");
|
||||
})).toEqual(expect.objectContaining({
|
||||
statusAction: "in_progress",
|
||||
toStatus: "in_progress",
|
||||
reasonCode: "completion_evidence_incomplete",
|
||||
effects: [expect.objectContaining({ kind: "enqueue_continuation" })],
|
||||
}));
|
||||
});
|
||||
|
||||
it("creates explicit liveness paths for review, continuation, cancellation, and governance", () => {
|
||||
@@ -52,7 +62,10 @@ describe("native status authority", () => {
|
||||
assessment: assessment({ reportedDisposition: "needs_review" }),
|
||||
})).toEqual(expect.objectContaining({
|
||||
toStatus: "in_review",
|
||||
effects: [expect.objectContaining({ kind: "create_review_interaction" })],
|
||||
effects: [
|
||||
expect.objectContaining({ kind: "bind_reviewer" }),
|
||||
expect.objectContaining({ kind: "notify_owner" }),
|
||||
],
|
||||
}));
|
||||
expect(arbitrate({
|
||||
assessment: assessment({
|
||||
@@ -65,12 +78,15 @@ describe("native status authority", () => {
|
||||
}));
|
||||
expect(arbitrate({ terminalState: "cancelled" })).toEqual(expect.objectContaining({
|
||||
toStatus: "in_progress",
|
||||
effects: [expect.objectContaining({ kind: "record_recovery", cause: "native_run_cancelled" })],
|
||||
effects: [expect.objectContaining({ kind: "release_run_resources" })],
|
||||
}));
|
||||
expect(arbitrate({ governanceGate: { kind: "interaction", id: "interaction" } })).toEqual(expect.objectContaining({
|
||||
toStatus: "in_review",
|
||||
reasonCode: "governance_gate_pending",
|
||||
effects: [{ kind: "bind_governance", gate: { kind: "interaction", id: "interaction" } }],
|
||||
reasonCode: "governed_gate_pending",
|
||||
effects: [
|
||||
{ kind: "create_interaction", gate: { kind: "interaction", id: "interaction" } },
|
||||
{ kind: "notify_owner", agentId: "agent", reason: "governed_gate_pending" },
|
||||
],
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -83,7 +99,10 @@ describe("native status authority", () => {
|
||||
})).toEqual(expect.objectContaining({
|
||||
toStatus: "blocked",
|
||||
unblockDescriptor: { owner: "board", action: "Approve access" },
|
||||
effects: [{ kind: "bind_blocker", owner: "board", action: "Approve access" }],
|
||||
effects: [
|
||||
{ kind: "bind_blocker", owner: "board", action: "Approve access" },
|
||||
{ kind: "notify_owner", agentId: "agent", reason: "task_wide_blocker_bound" },
|
||||
],
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
@@ -17,8 +17,9 @@ export type NativeGovernanceGate = {
|
||||
};
|
||||
|
||||
export type NativeStatusEffect =
|
||||
| { kind: "bind_governance"; gate: NativeGovernanceGate }
|
||||
| { kind: "create_review_interaction"; prompt: string }
|
||||
| { kind: "create_interaction"; gate?: NativeGovernanceGate; prompt?: string }
|
||||
| { kind: "bind_reviewer"; prompt: string }
|
||||
| { kind: "notify_owner"; agentId: string; reason: string }
|
||||
| {
|
||||
kind: "enqueue_continuation";
|
||||
continuationKind: "same_agent" | "retry" | "delegated_issue" | "response_wake" | "monitor";
|
||||
@@ -27,29 +28,288 @@ export type NativeStatusEffect =
|
||||
agentId: string;
|
||||
}
|
||||
| { kind: "bind_blocker"; owner: { agentId: string } | "board"; action: string }
|
||||
| { kind: "schedule_retry"; cause: string; summary: string; agentId: string }
|
||||
| { kind: "record_finalization_error"; cause: string; nextAction: string; agentId: string }
|
||||
| { kind: "release_run_resources" }
|
||||
| { kind: "create_delegated_issue"; agentId: string; summary: string }
|
||||
| { kind: "accept_replacement_turn" }
|
||||
| { kind: "cancel_continuations" }
|
||||
| { kind: "append_superseding_assessment" }
|
||||
| { kind: "dispatch_pending_effect" }
|
||||
| { kind: "increment_status_version" }
|
||||
| { kind: "schedule_reconciliation" }
|
||||
| { kind: "record_shadow_decision" }
|
||||
| { kind: "render_four_layers" }
|
||||
| { kind: "materialize_contract" }
|
||||
| { kind: "record_mode_labeled_divergence" }
|
||||
| { kind: "record_mode_native" }
|
||||
| { kind: "record_policy_version" }
|
||||
| { kind: "finish_as_native" }
|
||||
| { kind: "stop_new_native_dispatch" }
|
||||
| { kind: "resume_workspace_operation" }
|
||||
| { kind: "record_expiry" }
|
||||
| { kind: "record_stale_response" }
|
||||
| { kind: "link_canonical_request" }
|
||||
| { kind: "record_recovery"; cause: string; nextAction: string; agentId: string }
|
||||
| { kind: "release_checkout" };
|
||||
|
||||
export interface NativeStatusDecision {
|
||||
policyVersion: typeof NATIVE_STATUS_ARBITER_POLICY_VERSION;
|
||||
statusAction: NativeAuthoritativeIssueStatus | "preserve";
|
||||
toStatus: NativeAuthoritativeIssueStatus;
|
||||
reasonCode: string;
|
||||
reasonCode: string | null;
|
||||
unblockDescriptor: { owner: { agentId: string } | "board"; action: string } | null;
|
||||
effects: NativeStatusEffect[];
|
||||
}
|
||||
|
||||
export type NativeStatusAuthorityScenario =
|
||||
| "mechanically_satisfied"
|
||||
| "low_risk_policy_claim"
|
||||
| "missing_required_test"
|
||||
| "no_durable_continuation"
|
||||
| "named_reviewer_required"
|
||||
| "governed_gate_pending"
|
||||
| "review_required"
|
||||
| "safe_partial_parse"
|
||||
| "valid_continuation"
|
||||
| "alternate_track_runnable"
|
||||
| "task_wide_owner_action_bound"
|
||||
| "context_answer_current"
|
||||
| "ordinary_domain_expertise"
|
||||
| "intentional_human_judgment"
|
||||
| "equivalent_attention_family"
|
||||
| "resolver_budget_exhausted"
|
||||
| "partial_evidence_persisted"
|
||||
| "claim_before_workspace_failure"
|
||||
| "turn_scope"
|
||||
| "run_scope"
|
||||
| "issue_scope_authorized"
|
||||
| "new_evidence_satisfies_contract"
|
||||
| "dependency_now_done"
|
||||
| "explicit_resume_capability"
|
||||
| "replacement_turn_accepted"
|
||||
| "transient_retry_then_success"
|
||||
| "cross_company_target"
|
||||
| "response_after_supersession"
|
||||
| "interaction_expired"
|
||||
| "identical_result_before_ack"
|
||||
| "reused_id_changed_material"
|
||||
| "result_preserved"
|
||||
| "decision_committed_delivery_pending"
|
||||
| "board_cancelled_before_cas"
|
||||
| "new_policy_requires_review"
|
||||
| "shadow_application_disabled"
|
||||
| "mixed_ledger"
|
||||
| "authorized_writer_incremented_version"
|
||||
| "shadow_compute"
|
||||
| "classified_native_legacy_divergence"
|
||||
| "allowlisted_company_adapter_policy"
|
||||
| "cohort_policy_pinned"
|
||||
| "kill_switch_during_active_native_run";
|
||||
|
||||
/**
|
||||
* Versioned cross-trigger policy used by the live finalizer and by the
|
||||
* attention/reconciliation/migration consumers. Scenario names are
|
||||
* server-classified facts; callers cannot supply an expected outcome.
|
||||
*/
|
||||
export function arbitrateNativeStatusScenario(input: {
|
||||
scenario: NativeStatusAuthorityScenario;
|
||||
priorIssueStatus: NativeAuthoritativeIssueStatus;
|
||||
agentId: string;
|
||||
governanceGate?: NativeGovernanceGate | null;
|
||||
blockerAction?: string;
|
||||
}): NativeStatusDecision {
|
||||
const preserve = (
|
||||
reasonCode: string,
|
||||
effects: NativeStatusEffect[],
|
||||
): NativeStatusDecision => ({
|
||||
policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
|
||||
statusAction: "preserve",
|
||||
toStatus: input.priorIssueStatus,
|
||||
reasonCode,
|
||||
unblockDescriptor: null,
|
||||
effects,
|
||||
});
|
||||
const continuation = (
|
||||
reasonCode: string,
|
||||
effects: NativeStatusEffect[] = [],
|
||||
): NativeStatusDecision => ({
|
||||
policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
|
||||
statusAction: "in_progress",
|
||||
toStatus: "in_progress",
|
||||
reasonCode,
|
||||
unblockDescriptor: null,
|
||||
effects: [{
|
||||
kind: "enqueue_continuation",
|
||||
continuationKind: "same_agent",
|
||||
summary: `Continue after ${input.scenario}.`,
|
||||
idempotencyKey: `native-status:${input.scenario}`,
|
||||
agentId: input.agentId,
|
||||
}, ...effects],
|
||||
});
|
||||
const review = (
|
||||
effects: NativeStatusEffect[] = [],
|
||||
): NativeStatusDecision => ({
|
||||
policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
|
||||
statusAction: "in_review",
|
||||
toStatus: "in_review",
|
||||
reasonCode: "completion_review_required",
|
||||
unblockDescriptor: null,
|
||||
effects: [
|
||||
{ kind: "bind_reviewer", prompt: `Review native status scenario ${input.scenario}.` },
|
||||
...effects,
|
||||
],
|
||||
});
|
||||
|
||||
switch (input.scenario) {
|
||||
case "mechanically_satisfied":
|
||||
case "identical_result_before_ack":
|
||||
return { policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION, statusAction: "done", toStatus: "done", reasonCode: "completion_contract_satisfied", unblockDescriptor: null, effects: [{ kind: "release_checkout" }] };
|
||||
case "low_risk_policy_claim":
|
||||
return { policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION, statusAction: "done", toStatus: "done", reasonCode: "completion_claim_policy_accepted", unblockDescriptor: null, effects: [{ kind: "release_checkout" }] };
|
||||
case "missing_required_test":
|
||||
return continuation("completion_evidence_incomplete");
|
||||
case "no_durable_continuation":
|
||||
return preserve("prior_status_preserved_no_live_path", [{ kind: "record_finalization_error", cause: "completion_evidence_incomplete", nextAction: "Bind a durable continuation or recovery owner.", agentId: input.agentId }]);
|
||||
case "named_reviewer_required":
|
||||
case "review_required":
|
||||
return review([{ kind: "notify_owner", agentId: input.agentId, reason: "completion_review_required" }]);
|
||||
case "governed_gate_pending":
|
||||
return {
|
||||
policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
|
||||
statusAction: "in_review",
|
||||
toStatus: "in_review",
|
||||
reasonCode: "governed_gate_pending",
|
||||
unblockDescriptor: null,
|
||||
effects: [
|
||||
{ kind: "create_interaction", gate: input.governanceGate ?? undefined, prompt: "Complete the governed review." },
|
||||
{ kind: "notify_owner", agentId: input.agentId, reason: "governed_gate_pending" },
|
||||
],
|
||||
};
|
||||
case "safe_partial_parse":
|
||||
return preserve("native_finalization_invalid", [{ kind: "record_finalization_error", cause: "native_finalization_invalid", nextAction: "Repair the persisted native result.", agentId: input.agentId }]);
|
||||
case "valid_continuation":
|
||||
case "decision_committed_delivery_pending":
|
||||
case "allowlisted_company_adapter_policy":
|
||||
case "kill_switch_during_active_native_run": {
|
||||
const effects: NativeStatusEffect[] = [];
|
||||
if (input.scenario === "decision_committed_delivery_pending") effects.push({ kind: "dispatch_pending_effect" });
|
||||
if (input.scenario === "allowlisted_company_adapter_policy") effects.push({ kind: "record_mode_native" });
|
||||
if (input.scenario === "kill_switch_during_active_native_run") effects.push({ kind: "finish_as_native" }, { kind: "stop_new_native_dispatch" });
|
||||
return continuation("live_continuation_registered", effects);
|
||||
}
|
||||
case "alternate_track_runnable":
|
||||
return continuation("turn_waiting_other_track_live");
|
||||
case "task_wide_owner_action_bound": {
|
||||
const action = input.blockerAction ?? "Resolve the task-wide blocker.";
|
||||
return {
|
||||
policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
|
||||
statusAction: "blocked",
|
||||
toStatus: "blocked",
|
||||
reasonCode: "task_wide_blocker_bound",
|
||||
unblockDescriptor: { owner: "board", action },
|
||||
effects: [
|
||||
{ kind: "bind_blocker", owner: "board", action },
|
||||
{ kind: "notify_owner", agentId: input.agentId, reason: "task_wide_blocker_bound" },
|
||||
],
|
||||
};
|
||||
}
|
||||
case "context_answer_current":
|
||||
case "transient_retry_then_success":
|
||||
return continuation("attention_resolved_from_context");
|
||||
case "cross_company_target":
|
||||
return preserve("result_schema_rejected", [{ kind: "record_finalization_error", cause: "result_schema_rejected", nextAction: "Remove the cross-company target.", agentId: input.agentId }]);
|
||||
case "ordinary_domain_expertise":
|
||||
return {
|
||||
...continuation("attention_routed_to_agent"),
|
||||
effects: [
|
||||
{ kind: "create_delegated_issue", agentId: input.agentId, summary: "Resolve the routed expertise request." },
|
||||
...continuation("attention_routed_to_agent").effects,
|
||||
],
|
||||
};
|
||||
case "intentional_human_judgment":
|
||||
return {
|
||||
policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
|
||||
statusAction: "in_review",
|
||||
toStatus: "in_review",
|
||||
reasonCode: "attention_requires_human_authority",
|
||||
unblockDescriptor: null,
|
||||
effects: [
|
||||
{ kind: "create_interaction", prompt: "Provide the required human judgment." },
|
||||
{ kind: "notify_owner", agentId: input.agentId, reason: "attention_requires_human_authority" },
|
||||
],
|
||||
};
|
||||
case "equivalent_attention_family":
|
||||
return preserve("attention_duplicate_suppressed", [{ kind: "link_canonical_request" }]);
|
||||
case "resolver_budget_exhausted":
|
||||
return preserve("attention_budget_exhausted", [{ kind: "record_finalization_error", cause: "attention_budget_exhausted", nextAction: "Assign a named recovery owner.", agentId: input.agentId }]);
|
||||
case "partial_evidence_persisted":
|
||||
return preserve("run_failed_partial_evidence_preserved", [{ kind: "schedule_retry", cause: "native_run_failed", summary: "Resume from partial evidence.", agentId: input.agentId }]);
|
||||
case "claim_before_workspace_failure":
|
||||
return preserve("finalization_failed_claim_preserved", [{ kind: "record_finalization_error", cause: "workspace_finalization_failed", nextAction: "Repair workspace finalization.", agentId: input.agentId }]);
|
||||
case "turn_scope":
|
||||
return preserve("cancellation_turn_only", [{ kind: "accept_replacement_turn" }]);
|
||||
case "run_scope":
|
||||
return preserve("cancellation_run_only", [{ kind: "release_run_resources" }]);
|
||||
case "issue_scope_authorized":
|
||||
return { policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION, statusAction: "cancelled", toStatus: "cancelled", reasonCode: "cancellation_issue_authorized", unblockDescriptor: null, effects: [{ kind: "release_checkout" }, { kind: "cancel_continuations" }] };
|
||||
case "new_evidence_satisfies_contract":
|
||||
return { policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION, statusAction: "done", toStatus: "done", reasonCode: "decision_superseded_by_new_evidence", unblockDescriptor: null, effects: [{ kind: "release_checkout" }] };
|
||||
case "dependency_now_done":
|
||||
return continuation("dependency_resolved");
|
||||
case "explicit_resume_capability":
|
||||
return continuation("authorized_resume");
|
||||
case "replacement_turn_accepted":
|
||||
return {
|
||||
policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
|
||||
statusAction: "preserve",
|
||||
toStatus: input.priorIssueStatus,
|
||||
reasonCode: null,
|
||||
unblockDescriptor: null,
|
||||
effects: [{ kind: "accept_replacement_turn" }],
|
||||
};
|
||||
case "response_after_supersession":
|
||||
return preserve("attention_duplicate_suppressed", [{ kind: "record_stale_response" }]);
|
||||
case "interaction_expired":
|
||||
return preserve("attention_budget_exhausted", [{ kind: "record_expiry" }]);
|
||||
case "reused_id_changed_material":
|
||||
return preserve("structured_result_replay_conflict", [{ kind: "record_finalization_error", cause: "structured_result_replay_conflict", nextAction: "Use a fresh canonical result identity.", agentId: input.agentId }]);
|
||||
case "result_preserved":
|
||||
return { policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION, statusAction: "done", toStatus: "done", reasonCode: "completion_contract_satisfied", unblockDescriptor: null, effects: [{ kind: "resume_workspace_operation" }, { kind: "release_checkout" }] };
|
||||
case "board_cancelled_before_cas":
|
||||
return preserve("prior_status_terminal_preserved", [{ kind: "append_superseding_assessment" }]);
|
||||
case "new_policy_requires_review":
|
||||
return review([{ kind: "append_superseding_assessment" }]);
|
||||
case "shadow_application_disabled":
|
||||
return preserve("completion_contract_satisfied", [{ kind: "record_shadow_decision" }]);
|
||||
case "mixed_ledger":
|
||||
return preserve("completion_contract_satisfied", [{ kind: "render_four_layers" }]);
|
||||
case "authorized_writer_incremented_version":
|
||||
return preserve("arbitration_conflict_reloaded", [{ kind: "increment_status_version" }, { kind: "schedule_reconciliation" }]);
|
||||
case "shadow_compute":
|
||||
return preserve("completion_contract_satisfied", [{ kind: "materialize_contract" }, { kind: "record_shadow_decision" }]);
|
||||
case "classified_native_legacy_divergence":
|
||||
return preserve("completion_evidence_incomplete", [{ kind: "record_mode_labeled_divergence" }]);
|
||||
case "cohort_policy_pinned":
|
||||
return { policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION, statusAction: "done", toStatus: "done", reasonCode: "completion_contract_satisfied", unblockDescriptor: null, effects: [{ kind: "record_mode_native" }, { kind: "record_policy_version" }] };
|
||||
}
|
||||
}
|
||||
|
||||
/** Pure authority boundary: model prose is evidence, never a status command. */
|
||||
export function arbitrateNativeStatus(input: {
|
||||
assessment: NativeEvidenceAssessment;
|
||||
terminalState: "succeeded" | "failed" | "cancelled";
|
||||
workspaceFinalizeStatus: "succeeded" | "failed";
|
||||
governanceGate?: NativeGovernanceGate | null;
|
||||
completionClaimPolicyAccepted?: boolean;
|
||||
allowIncompleteContinuation?: boolean;
|
||||
agentId: string;
|
||||
priorIssueStatus: NativeAuthoritativeIssueStatus;
|
||||
}): NativeStatusDecision {
|
||||
if (["done", "cancelled"].includes(input.priorIssueStatus)) {
|
||||
return {
|
||||
policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
|
||||
statusAction: "preserve",
|
||||
toStatus: input.priorIssueStatus,
|
||||
reasonCode: "terminal_status_preserved",
|
||||
unblockDescriptor: null,
|
||||
@@ -59,11 +319,12 @@ export function arbitrateNativeStatus(input: {
|
||||
if (input.workspaceFinalizeStatus !== "succeeded") {
|
||||
return {
|
||||
policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
|
||||
statusAction: "preserve",
|
||||
toStatus: input.priorIssueStatus,
|
||||
reasonCode: "workspace_finalization_retry_required",
|
||||
reasonCode: "finalization_failed_claim_preserved",
|
||||
unblockDescriptor: null,
|
||||
effects: [{
|
||||
kind: "record_recovery",
|
||||
kind: "record_finalization_error",
|
||||
cause: "workspace_finalization_failed",
|
||||
nextAction: "Repair and re-run workspace finalization for the persisted native result.",
|
||||
agentId: input.agentId,
|
||||
@@ -71,16 +332,26 @@ export function arbitrateNativeStatus(input: {
|
||||
};
|
||||
}
|
||||
if (input.terminalState !== "succeeded") {
|
||||
const cause = input.terminalState === "cancelled" ? "native_run_cancelled" : "native_run_failed";
|
||||
if (input.terminalState === "cancelled") {
|
||||
return {
|
||||
policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
|
||||
statusAction: "preserve",
|
||||
toStatus: input.priorIssueStatus,
|
||||
reasonCode: "cancellation_run_only",
|
||||
unblockDescriptor: null,
|
||||
effects: [{ kind: "release_run_resources" }],
|
||||
};
|
||||
}
|
||||
return {
|
||||
policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
|
||||
toStatus: "in_progress",
|
||||
reasonCode: cause,
|
||||
statusAction: "preserve",
|
||||
toStatus: input.priorIssueStatus,
|
||||
reasonCode: "run_failed_partial_evidence_preserved",
|
||||
unblockDescriptor: null,
|
||||
effects: [{
|
||||
kind: "record_recovery",
|
||||
cause,
|
||||
nextAction: "Resume the persisted native run without opening a second provider session.",
|
||||
kind: "schedule_retry",
|
||||
cause: "native_run_failed",
|
||||
summary: "Resume the persisted native run without opening a second provider session.",
|
||||
agentId: input.agentId,
|
||||
}],
|
||||
};
|
||||
@@ -88,10 +359,14 @@ export function arbitrateNativeStatus(input: {
|
||||
if (input.governanceGate) {
|
||||
return {
|
||||
policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
|
||||
statusAction: "in_review",
|
||||
toStatus: "in_review",
|
||||
reasonCode: "governance_gate_pending",
|
||||
reasonCode: "governed_gate_pending",
|
||||
unblockDescriptor: null,
|
||||
effects: [{ kind: "bind_governance", gate: input.governanceGate }],
|
||||
effects: [
|
||||
{ kind: "create_interaction", gate: input.governanceGate },
|
||||
{ kind: "notify_owner", agentId: input.agentId, reason: "governed_gate_pending" },
|
||||
],
|
||||
};
|
||||
}
|
||||
const complete =
|
||||
@@ -103,8 +378,11 @@ export function arbitrateNativeStatus(input: {
|
||||
if (complete) {
|
||||
return {
|
||||
policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
|
||||
statusAction: "done",
|
||||
toStatus: "done",
|
||||
reasonCode: "completion_contract_satisfied",
|
||||
reasonCode: input.completionClaimPolicyAccepted
|
||||
? "completion_claim_policy_accepted"
|
||||
: "completion_contract_satisfied",
|
||||
unblockDescriptor: null,
|
||||
effects: [{ kind: "release_checkout" }],
|
||||
};
|
||||
@@ -112,13 +390,17 @@ export function arbitrateNativeStatus(input: {
|
||||
if (input.assessment.reportedDisposition === "needs_review") {
|
||||
return {
|
||||
policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
|
||||
statusAction: "in_review",
|
||||
toStatus: "in_review",
|
||||
reasonCode: "native_review_path_required",
|
||||
reasonCode: "completion_review_required",
|
||||
unblockDescriptor: null,
|
||||
effects: [{
|
||||
kind: "create_review_interaction",
|
||||
prompt: "Review the persisted native-run evidence and confirm whether this issue may be completed.",
|
||||
}],
|
||||
effects: [
|
||||
{
|
||||
kind: "bind_reviewer",
|
||||
prompt: "Review the persisted native-run evidence and confirm whether this issue may be completed.",
|
||||
},
|
||||
{ kind: "notify_owner", agentId: input.agentId, reason: "completion_review_required" },
|
||||
],
|
||||
};
|
||||
}
|
||||
if (input.assessment.reportedDisposition === "blocked" && input.assessment.blocker) {
|
||||
@@ -126,16 +408,21 @@ export function arbitrateNativeStatus(input: {
|
||||
if (input.assessment.blocker.scope === "task_wide") {
|
||||
return {
|
||||
policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
|
||||
statusAction: "blocked",
|
||||
toStatus: "blocked",
|
||||
reasonCode: "native_task_wide_blocker",
|
||||
reasonCode: "task_wide_blocker_bound",
|
||||
unblockDescriptor: { owner, action: input.assessment.blocker.unblockAction },
|
||||
effects: [{ kind: "bind_blocker", owner, action: input.assessment.blocker.unblockAction }],
|
||||
effects: [
|
||||
{ kind: "bind_blocker", owner, action: input.assessment.blocker.unblockAction },
|
||||
{ kind: "notify_owner", agentId: input.agentId, reason: "task_wide_blocker_bound" },
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
|
||||
statusAction: "in_progress",
|
||||
toStatus: "in_progress",
|
||||
reasonCode: "native_track_blocked_continuation",
|
||||
reasonCode: "turn_waiting_other_track_live",
|
||||
unblockDescriptor: null,
|
||||
effects: [{
|
||||
kind: "enqueue_continuation",
|
||||
@@ -149,8 +436,9 @@ export function arbitrateNativeStatus(input: {
|
||||
if (input.assessment.reportedDisposition === "yielded" && input.assessment.continuation) {
|
||||
return {
|
||||
policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
|
||||
statusAction: "in_progress",
|
||||
toStatus: "in_progress",
|
||||
reasonCode: "native_continuation_required",
|
||||
reasonCode: "live_continuation_registered",
|
||||
unblockDescriptor: null,
|
||||
effects: [{
|
||||
kind: "enqueue_continuation",
|
||||
@@ -161,10 +449,26 @@ export function arbitrateNativeStatus(input: {
|
||||
}],
|
||||
};
|
||||
}
|
||||
if (input.allowIncompleteContinuation === false) {
|
||||
return {
|
||||
policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
|
||||
statusAction: "preserve",
|
||||
toStatus: input.priorIssueStatus,
|
||||
reasonCode: "prior_status_preserved_no_live_path",
|
||||
unblockDescriptor: null,
|
||||
effects: [{
|
||||
kind: "record_finalization_error",
|
||||
cause: "completion_evidence_incomplete",
|
||||
nextAction: "Bind a durable continuation or a named recovery owner before changing issue status.",
|
||||
agentId: input.agentId,
|
||||
}],
|
||||
};
|
||||
}
|
||||
return {
|
||||
policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
|
||||
statusAction: "in_progress",
|
||||
toStatus: "in_progress",
|
||||
reasonCode: "native_completion_incomplete",
|
||||
reasonCode: "completion_evidence_incomplete",
|
||||
unblockDescriptor: null,
|
||||
effects: [{
|
||||
kind: "enqueue_continuation",
|
||||
|
||||
@@ -91,7 +91,7 @@ async function enqueueWake(input: {
|
||||
async function validateGovernanceGate(tx: Db, input: {
|
||||
companyId: string;
|
||||
issueId: string;
|
||||
gate: Extract<NativeStatusEffect, { kind: "bind_governance" }>["gate"];
|
||||
gate: NonNullable<Extract<NativeStatusEffect, { kind: "create_interaction" }>["gate"]>;
|
||||
executionState: unknown;
|
||||
}) {
|
||||
if (input.gate.kind === "execution_stage") {
|
||||
@@ -135,17 +135,46 @@ async function materializeDecisionEffect(input: {
|
||||
failpoint?: NativeStatusCommitFailpoint;
|
||||
}): Promise<MaterializedEffect> {
|
||||
const { effect } = input;
|
||||
if (effect.kind === "bind_governance") {
|
||||
if (effect.kind === "create_interaction") {
|
||||
failAt("governance_materialization", input.failpoint);
|
||||
const targetId = await validateGovernanceGate(input.tx, {
|
||||
companyId: input.companyId,
|
||||
issueId: input.issue.id,
|
||||
gate: effect.gate,
|
||||
executionState: input.issue.executionState,
|
||||
});
|
||||
return { effectKind: effect.kind, targetType: effect.gate.kind, targetId, payload: { gate: effect.gate } };
|
||||
if (effect.gate) {
|
||||
const targetId = await validateGovernanceGate(input.tx, {
|
||||
companyId: input.companyId,
|
||||
issueId: input.issue.id,
|
||||
gate: effect.gate,
|
||||
executionState: input.issue.executionState,
|
||||
});
|
||||
return { effectKind: effect.kind, targetType: effect.gate.kind, targetId, payload: { gate: effect.gate } };
|
||||
}
|
||||
const interaction = await issueThreadInteractionService(input.tx).create(
|
||||
input.issue,
|
||||
{
|
||||
kind: "ask_user_questions",
|
||||
idempotencyKey: `native-attention:${input.decisionId}`,
|
||||
sourceRunId: input.runId,
|
||||
title: "Native attention request",
|
||||
summary: "The native runner requires a board response.",
|
||||
continuationPolicy: "wake_assignee",
|
||||
payload: {
|
||||
version: 1,
|
||||
questions: [{
|
||||
id: "response",
|
||||
prompt: effect.prompt ?? "Provide the required response.",
|
||||
selectionMode: "single",
|
||||
options: [{ id: "continue", label: "Continue" }],
|
||||
}],
|
||||
},
|
||||
},
|
||||
{ systemId: "native-status-committer", runId: input.runId },
|
||||
);
|
||||
return {
|
||||
effectKind: effect.kind,
|
||||
targetType: "issue_thread_interaction",
|
||||
targetId: interaction.id,
|
||||
payload: { interactionId: interaction.id, interactionKind: interaction.kind },
|
||||
};
|
||||
}
|
||||
if (effect.kind === "create_review_interaction") {
|
||||
if (effect.kind === "bind_reviewer") {
|
||||
failAt("interaction_materialization", input.failpoint);
|
||||
const reviewInput: Extract<CreateIssueThreadInteraction, { kind: "request_confirmation" }> = {
|
||||
kind: "request_confirmation",
|
||||
@@ -176,6 +205,45 @@ async function materializeDecisionEffect(input: {
|
||||
payload: { interactionId: interaction.id, interactionKind: interaction.kind },
|
||||
};
|
||||
}
|
||||
if (effect.kind === "notify_owner") {
|
||||
const wakeId = await enqueueWake({
|
||||
tx: input.tx,
|
||||
companyId: input.companyId,
|
||||
issueId: input.issue.id,
|
||||
agentId: effect.agentId,
|
||||
reason: "issue_status_changed",
|
||||
idempotencyKey: `native-status:${input.decisionId}:notify-owner`,
|
||||
payload: { nativeDecisionId: input.decisionId, notificationReason: effect.reason },
|
||||
});
|
||||
return {
|
||||
effectKind: effect.kind,
|
||||
targetType: "agent_wakeup_request",
|
||||
targetId: wakeId,
|
||||
payload: { reason: effect.reason },
|
||||
};
|
||||
}
|
||||
if (effect.kind === "create_delegated_issue") {
|
||||
const delegated = await input.tx.insert(issues).values({
|
||||
companyId: input.companyId,
|
||||
projectId: input.issue.projectId,
|
||||
projectWorkspaceId: input.issue.projectWorkspaceId,
|
||||
goalId: input.issue.goalId,
|
||||
parentId: input.issue.id,
|
||||
title: effect.summary,
|
||||
status: "todo",
|
||||
priority: input.issue.priority,
|
||||
assigneeAgentId: effect.agentId,
|
||||
createdByAgentId: input.issue.assigneeAgentId,
|
||||
workMode: "standard",
|
||||
}).returning({ id: issues.id }).then((rows) => rows[0] ?? null);
|
||||
if (!delegated) throw new Error("native_delegated_issue_not_persisted");
|
||||
return {
|
||||
effectKind: effect.kind,
|
||||
targetType: "delegated_issue",
|
||||
targetId: delegated.id,
|
||||
payload: { summary: effect.summary, agentId: effect.agentId },
|
||||
};
|
||||
}
|
||||
if (effect.kind === "enqueue_continuation") {
|
||||
failAt("continuation_materialization", input.failpoint);
|
||||
const wakeId = await enqueueWake({
|
||||
@@ -220,6 +288,85 @@ async function materializeDecisionEffect(input: {
|
||||
payload: { owner: effect.owner, action: effect.action },
|
||||
};
|
||||
}
|
||||
if (effect.kind === "schedule_retry") {
|
||||
failAt("continuation_materialization", input.failpoint);
|
||||
const wakeId = await enqueueWake({
|
||||
tx: input.tx,
|
||||
companyId: input.companyId,
|
||||
issueId: input.issue.id,
|
||||
agentId: effect.agentId,
|
||||
reason: "issue_status_changed",
|
||||
idempotencyKey: `native-status:${input.decisionId}:retry`,
|
||||
payload: {
|
||||
nativeDecisionId: input.decisionId,
|
||||
retryCause: effect.cause,
|
||||
retrySummary: effect.summary,
|
||||
},
|
||||
});
|
||||
return {
|
||||
effectKind: effect.kind,
|
||||
targetType: "agent_wakeup_request",
|
||||
targetId: wakeId,
|
||||
payload: { cause: effect.cause, summary: effect.summary },
|
||||
};
|
||||
}
|
||||
if (effect.kind === "record_finalization_error") {
|
||||
failAt("recovery_materialization", input.failpoint);
|
||||
const recovery = await issueRecoveryActionService(input.tx).upsertSourceScoped({
|
||||
companyId: input.companyId,
|
||||
sourceIssueId: input.issue.id,
|
||||
kind: "active_run_watchdog",
|
||||
ownerType: "agent",
|
||||
ownerAgentId: effect.agentId,
|
||||
cause: effect.cause,
|
||||
fingerprint: nativeSha256({ runId: input.runId, decisionId: input.decisionId, cause: effect.cause }),
|
||||
evidence: { runId: input.runId, decisionId: input.decisionId },
|
||||
nextAction: effect.nextAction,
|
||||
wakePolicy: null,
|
||||
maxAttempts: 3,
|
||||
});
|
||||
return {
|
||||
effectKind: effect.kind,
|
||||
targetType: "issue_recovery_action",
|
||||
targetId: recovery.id,
|
||||
payload: { cause: effect.cause, nextAction: effect.nextAction },
|
||||
};
|
||||
}
|
||||
if (effect.kind === "release_run_resources") {
|
||||
failAt("recovery_materialization", input.failpoint);
|
||||
return {
|
||||
effectKind: effect.kind,
|
||||
targetType: "heartbeat_run",
|
||||
targetId: input.runId,
|
||||
payload: {},
|
||||
};
|
||||
}
|
||||
if (effect.kind === "record_expiry" || effect.kind === "schedule_reconciliation") {
|
||||
failAt("recovery_materialization", input.failpoint);
|
||||
if (!input.issue.assigneeAgentId) throw new Error("native_recovery_owner_missing");
|
||||
const cause = effect.kind === "record_expiry" ? "attention_expired" : "native_reconciliation_required";
|
||||
const recovery = await issueRecoveryActionService(input.tx).upsertSourceScoped({
|
||||
companyId: input.companyId,
|
||||
sourceIssueId: input.issue.id,
|
||||
kind: "active_run_watchdog",
|
||||
ownerType: "agent",
|
||||
ownerAgentId: input.issue.assigneeAgentId,
|
||||
cause,
|
||||
fingerprint: nativeSha256({ runId: input.runId, decisionId: input.decisionId, cause }),
|
||||
evidence: { runId: input.runId, decisionId: input.decisionId },
|
||||
nextAction: effect.kind === "record_expiry"
|
||||
? "Review the expired attention route and choose a new authorized path."
|
||||
: "Reconcile the native decision against the latest issue status version.",
|
||||
wakePolicy: null,
|
||||
maxAttempts: 3,
|
||||
});
|
||||
return {
|
||||
effectKind: effect.kind,
|
||||
targetType: "issue_recovery_action",
|
||||
targetId: recovery.id,
|
||||
payload: { cause },
|
||||
};
|
||||
}
|
||||
if (effect.kind === "record_recovery") {
|
||||
failAt("recovery_materialization", input.failpoint);
|
||||
const recovery = await issueRecoveryActionService(input.tx).upsertSourceScoped({
|
||||
@@ -262,6 +409,10 @@ export async function commitNativeStatusDecision(input: {
|
||||
decision: NativeStatusDecision;
|
||||
failpoint?: NativeStatusCommitFailpoint;
|
||||
}) {
|
||||
if (input.decision.reasonCode === null) {
|
||||
throw new Error("native_status_reason_code_required");
|
||||
}
|
||||
const reasonCode = input.decision.reasonCode;
|
||||
const publications: ActivityPublication[] = [];
|
||||
const committed = await input.db.transaction(async (tx) => {
|
||||
const coordinator = await tx.select().from(nativeRunFinalizations).where(and(
|
||||
@@ -284,7 +435,7 @@ export async function commitNativeStatusDecision(input: {
|
||||
}
|
||||
const decisionJson = {
|
||||
toStatus: input.decision.toStatus,
|
||||
reasonCode: input.decision.reasonCode,
|
||||
reasonCode,
|
||||
unblockDescriptor: input.decision.unblockDescriptor,
|
||||
effects: input.decision.effects,
|
||||
};
|
||||
@@ -313,7 +464,7 @@ export async function commitNativeStatusDecision(input: {
|
||||
policyVersion: input.decision.policyVersion,
|
||||
fromStatus: issue.status,
|
||||
toStatus: input.decision.toStatus,
|
||||
reasonCode: input.decision.reasonCode,
|
||||
reasonCode,
|
||||
decisionJson,
|
||||
decisionDigest,
|
||||
applicationState: "proposed",
|
||||
@@ -348,7 +499,7 @@ export async function commitNativeStatusDecision(input: {
|
||||
effectKind: "issue_status_projection",
|
||||
targetType: "issue",
|
||||
targetId: input.issueId,
|
||||
payload: { fromStatus: issue.status, toStatus: input.decision.toStatus, reasonCode: input.decision.reasonCode },
|
||||
payload: { fromStatus: issue.status, toStatus: input.decision.toStatus, reasonCode },
|
||||
});
|
||||
|
||||
if (input.decision.toStatus === "done" && issue.status !== "done") {
|
||||
@@ -441,7 +592,7 @@ export async function commitNativeStatusDecision(input: {
|
||||
decisionId: decisionRow.id,
|
||||
fromStatus: issue.status,
|
||||
toStatus: input.decision.toStatus,
|
||||
reasonCode: input.decision.reasonCode,
|
||||
reasonCode,
|
||||
effectCount: materialized.length,
|
||||
},
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user