@piagent/platform 1.5.5 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +117 -0
- package/README.md +9 -8
- package/README.vi.md +9 -5
- package/adapters/web-frontend/profile.json +2 -4
- package/architecture/layers.json +18 -3
- package/benchmarks/deep-logic-v1/SPECIALIST_PROTOCOL.vi.md +122 -0
- package/benchmarks/deep-logic-v1/grade.mjs +272 -10
- package/benchmarks/deep-logic-v1/project/apps/admin/src/billing-summary.js +3 -0
- package/benchmarks/deep-logic-v1/project/benchmark-contract/public-smoke.test.js +166 -0
- package/benchmarks/deep-logic-v1/project/package.json +1 -1
- package/benchmarks/deep-logic-v1/project/packages/billing/src/close-period.js +3 -0
- package/benchmarks/deep-logic-v1/project/packages/billing/src/plan-timeline.js +3 -0
- package/benchmarks/deep-logic-v1/project/test/smoke.test.js +7 -1
- package/benchmarks/deep-logic-v1/prompts/budgeted-context-graph.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/fair-dependency-scheduler.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/layered-policy-resolution.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/resumable-stream-assembly.md +3 -1
- package/benchmarks/deep-logic-v1/prompts/revision-event-reconciliation.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/temporal-usage-billing-close.md +123 -0
- package/benchmarks/deep-logic-v1/prompts/transactional-config-merge.md +2 -0
- package/benchmarks/deep-logic-v1/suite.json +51 -3
- package/benchmarks/deep-logic-v1/variant.mjs +22 -1
- package/benchmarks/production-v1/spend-control.v1.json +68 -0
- package/benchmarks/production-v1/suite.json +42 -3
- package/catalog/capabilities.json +6 -6
- package/docs/command-reference-vietnamese.md +6 -6
- package/docs/context-engine.md +7 -1
- package/docs/distribution-standard.md +7 -7
- package/docs/en/architecture.md +1 -0
- package/docs/languages.json +4 -4
- package/docs/model-options.md +8 -5
- package/docs/operator-manual-vietnamese.md +13 -11
- package/docs/project-adapters.md +2 -2
- package/docs/publishing-for-teams.md +5 -0
- package/docs/quality-benchmark.md +271 -62
- package/docs/release-install-policy.md +10 -9
- package/docs/runtime-policy-design.md +4 -4
- package/docs/runtime-quality-baseline.md +13 -6
- package/docs/security-threat-model.md +2 -2
- package/docs/task-lifecycle-vietnamese.md +2 -2
- package/docs/team-onboarding.md +2 -2
- package/docs/usage-observability.md +7 -2
- package/docs/vi/architecture.md +1 -0
- package/docs-site/architecture.html +1 -1
- package/docs-site/benchmark.html +1 -1
- package/docs-site/commands.html +1 -1
- package/docs-site/context-engine.html +1 -1
- package/docs-site/en/architecture.html +1 -1
- package/docs-site/en/benchmark.html +1 -1
- package/docs-site/en/commands.html +1 -1
- package/docs-site/en/context-engine.html +1 -1
- package/docs-site/en/index.html +1 -1
- package/docs-site/en/mcp-approval.html +1 -1
- package/docs-site/en/mcp-auth.html +1 -1
- package/docs-site/en/mcp-servers.html +1 -1
- package/docs-site/en/mcp.html +1 -1
- package/docs-site/en/permissions.html +1 -1
- package/docs-site/en/profiles.html +1 -1
- package/docs-site/en/quickstart.html +1 -1
- package/docs-site/en/runtime.html +1 -1
- package/docs-site/en/scope.html +1 -1
- package/docs-site/en/security.html +1 -1
- package/docs-site/en/team.html +1 -1
- package/docs-site/en/whats-new.html +1 -1
- package/docs-site/en/workflows.html +1 -1
- package/docs-site/index.html +1 -1
- package/docs-site/mcp-approval.html +1 -1
- package/docs-site/mcp-auth.html +1 -1
- package/docs-site/mcp-servers.html +1 -1
- package/docs-site/mcp.html +1 -1
- package/docs-site/permissions.html +1 -1
- package/docs-site/profiles.html +1 -1
- package/docs-site/quickstart.html +1 -1
- package/docs-site/runtime.html +1 -1
- package/docs-site/scope.html +1 -1
- package/docs-site/security.html +1 -1
- package/docs-site/team.html +1 -1
- package/docs-site/whats-new.html +1 -1
- package/docs-site/workflows.html +1 -1
- package/evals/fixtures/benchmark-assurance-evidence.invalid.json +1 -1
- package/evals/fixtures/benchmark-assurance-evidence.valid.json +1 -1
- package/evals/fixtures/piagent-webui/session-live-state-v1.invalid.json +18 -0
- package/evals/fixtures/piagent-webui/session-live-state-v1.valid.json +27 -0
- package/evals/fs4-readiness-matrix.v1.json +2 -2
- package/evals/fs5-causal-arm.v1.json +2 -2
- package/evals/fs5-pilot-protocol.v1.json +2 -2
- package/evals/fs5-pilot-protocol.v4.json +6 -5
- package/evals/fs5-pilot-protocol.v5.json +7 -6
- package/evals/private-holdout-v1/public-exposure.v1.json +1 -1
- package/package.json +3 -1
- package/packages/piagent-core/benchmark/benchmark-assurance.js +4 -1
- package/packages/piagent-core/benchmark/benchmark-bootstrap.js +91 -9
- package/packages/piagent-core/benchmark/benchmark-claim-restrictions.js +38 -7
- package/packages/piagent-core/benchmark/benchmark-cli.js +7 -7
- package/packages/piagent-core/benchmark/benchmark-codex.js +3 -1
- package/packages/piagent-core/benchmark/benchmark-comparison.js +271 -2
- package/packages/piagent-core/benchmark/benchmark-core.js +732 -661
- package/packages/piagent-core/benchmark/benchmark-execution-guard.js +7 -5
- package/packages/piagent-core/benchmark/benchmark-forensics.js +439 -4
- package/packages/piagent-core/benchmark/benchmark-host-readiness.js +639 -0
- package/packages/piagent-core/benchmark/benchmark-matrix.js +22 -4
- package/packages/piagent-core/benchmark/benchmark-normalized-cost.js +317 -0
- package/packages/piagent-core/benchmark/benchmark-preflight.js +3 -1
- package/packages/piagent-core/benchmark/benchmark-process.js +6 -5
- package/packages/piagent-core/benchmark/benchmark-provider-wire.js +126 -0
- package/packages/piagent-core/benchmark/benchmark-record-validation.js +183 -0
- package/packages/piagent-core/benchmark/benchmark-report.js +116 -12
- package/packages/piagent-core/benchmark/benchmark-resume-recovery.js +14 -0
- package/packages/piagent-core/benchmark/benchmark-runner-policy.js +26 -0
- package/packages/piagent-core/benchmark/benchmark-stage-diagnostic.js +782 -0
- package/packages/piagent-core/benchmark/benchmark-statistics.js +64 -0
- package/packages/piagent-core/benchmark/benchmark-stop-policy.js +8 -5
- package/packages/piagent-core/benchmark/benchmark-suite-identity.js +31 -0
- package/packages/piagent-core/benchmark/benchmark-suite-runtime.js +13 -9
- package/packages/piagent-core/benchmark/benchmark-suite.js +128 -2
- package/packages/piagent-core/benchmark/benchmark-summary-support.js +376 -0
- package/packages/piagent-core/benchmark/benchmark-timing-diagnostics.js +971 -0
- package/packages/piagent-core/benchmark/benchmark-usage.js +311 -0
- package/packages/piagent-core/benchmark/benchmark-workflow.js +166 -0
- package/packages/piagent-core/docs/context-efficiency-improvement-plan.md +1080 -0
- package/packages/piagent-core/extensions/acceptance-contract-semantics.js +83 -69
- package/packages/piagent-core/extensions/acceptance-error-classes.js +160 -0
- package/packages/piagent-core/extensions/acceptance-executable-evidence.js +12 -9
- package/packages/piagent-core/extensions/acceptance-receipt.js +34 -11
- package/packages/piagent-core/extensions/acceptance-state-machine-evidence.js +518 -0
- package/packages/piagent-core/extensions/acceptance-test-binding-evidence.js +60 -0
- package/packages/piagent-core/extensions/context-efficiency-metrics.js +302 -0
- package/packages/piagent-core/extensions/context-efficiency-report.js +167 -0
- package/packages/piagent-core/extensions/context-engine.js +148 -145
- package/packages/piagent-core/extensions/context-evidence.js +48 -0
- package/packages/piagent-core/extensions/context-import-links.js +296 -0
- package/packages/piagent-core/extensions/context-telemetry-integrity.js +65 -0
- package/packages/piagent-core/extensions/criterion-context-pack.js +422 -7
- package/packages/piagent-core/extensions/criterion-graph.js +78 -8
- package/packages/piagent-core/extensions/guard-types.ts +2 -0
- package/packages/piagent-core/extensions/javascript-regex-evidence.js +32 -0
- package/packages/piagent-core/extensions/piagent-guard.ts +233 -41
- package/packages/piagent-core/extensions/task-contract-view.js +17 -3
- package/packages/piagent-core/extensions/task-lifecycle.js +7 -2
- package/packages/piagent-core/extensions/task-state.js +40 -134
- package/packages/piagent-core/extensions/verification-intelligence.js +3 -1
- package/packages/piagent-core/extensions/workspace-evidence-roots.js +157 -0
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/policies/base-policy.json +2 -0
- package/packages/piagent-core/runtime/context/context-delivery.ts +103 -0
- package/packages/piagent-core/runtime/context/context-delta-shadow.ts +53 -0
- package/packages/piagent-core/runtime/context/context-efficiency-format.ts +47 -0
- package/packages/piagent-core/runtime/context/context-evidence-qualification.ts +32 -0
- package/packages/piagent-core/runtime/context/prefix-telemetry.ts +47 -0
- package/packages/piagent-core/runtime/context/repository-memory-hints.ts +27 -0
- package/packages/piagent-core/runtime/hooks/agent-prompt-telemetry.ts +56 -0
- package/packages/piagent-core/runtime/hooks/agent-start-hook.ts +162 -102
- package/packages/piagent-core/runtime/hooks/completion-hook.ts +5 -6
- package/packages/piagent-core/runtime/hooks/input-hook.ts +11 -1
- package/packages/piagent-core/runtime/hooks/session-hooks.ts +22 -2
- package/packages/piagent-core/runtime/hooks/session-start-hook.ts +7 -2
- package/packages/piagent-core/runtime/hooks/tool-result-content-guards.ts +95 -0
- package/packages/piagent-core/runtime/hooks/tool-result-hook.ts +84 -102
- package/packages/piagent-core/runtime/inspection/activity-event-adapter.ts +4 -2
- package/packages/piagent-core/runtime/inspection/activity-recovery.ts +104 -0
- package/packages/piagent-core/runtime/inspection/task-compaction-history.ts +1 -1
- package/packages/piagent-core/runtime/inspection/task-handoff-history.ts +1 -1
- package/packages/piagent-core/runtime/inspection/tool-failure-classification.ts +53 -0
- package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +34 -6
- package/packages/piagent-core/runtime/model/authenticated-catalog.ts +17 -9
- package/packages/piagent-core/runtime/model/openai-codex-reasoning.ts +83 -0
- package/packages/piagent-core/runtime/model/provider-wire-fingerprint.ts +238 -0
- package/packages/piagent-core/runtime/orchestration/role-policy.ts +1 -1
- package/packages/piagent-core/runtime/product/activity-inspector.ts +7 -2
- package/packages/piagent-core/runtime/product/efficiency-metrics.ts +4 -2
- package/packages/piagent-core/runtime/quality/exact-output-contract.ts +2 -1
- package/packages/piagent-core/runtime/quality/performance-assurance-signals.ts +23 -0
- package/packages/piagent-core/runtime/quality/performance-assurance.ts +6 -2
- package/packages/piagent-core/runtime/recovery/edit-recovery-context.ts +113 -0
- package/packages/piagent-core/runtime/recovery/edit-recovery-delivery.ts +42 -0
- package/packages/piagent-core/runtime/recovery/semantic-repair-handshake.ts +55 -2
- package/packages/piagent-core/runtime/recovery/semantic-repair-runtime.ts +55 -2
- package/packages/piagent-core/runtime/registration/context-commands.ts +66 -5
- package/packages/piagent-core/runtime/registration/policy-tools.ts +46 -11
- package/packages/piagent-core/runtime/registration/task-completion-tools.ts +49 -16
- package/packages/piagent-core/runtime/registration/task-start-guidance.ts +163 -0
- package/packages/piagent-core/runtime/registration/task-start-retry-helpers.ts +37 -0
- package/packages/piagent-core/runtime/registration/task-start-tool.ts +67 -62
- package/packages/piagent-core/runtime/session/runtime-state.ts +154 -2
- package/packages/piagent-core/runtime/session/system-prompt.ts +4 -4
- package/packages/piagent-core/runtime/tools/apply-patch-tool.ts +500 -0
- package/packages/piagent-core/runtime/trajectory/trajectory-runtime.ts +2 -1
- package/packages/piagent-core/runtime/workflows/task-intake.ts +43 -2
- package/packages/piagent-core/security/sensitive-data.js +13 -218
- package/packages/piagent-core/security/sensitive-project-file.js +42 -0
- package/packages/piagent-core/security/sensitive-source-expression.js +330 -0
- package/packages/piagent-core/security/sensitive-source-formats.js +394 -0
- package/packages/piagent-core/security/sensitive-text.js +234 -0
- package/packages/piagent-core/subagents/piagent-worker.md +1 -1
- package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-rD9WJKxs.js → MarkdownMessage-CxeZqTte.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/index-DynIMe8I.js +176 -0
- package/packages/piagent-webui/dist/client/index.html +1 -1
- package/packages/piagent-webui/gateway/gateway-events.ts +109 -5
- package/packages/piagent-webui/gateway/gateway-service.ts +3 -0
- package/packages/piagent-webui/gateway/gateway-session-stream.ts +93 -7
- package/packages/piagent-webui/gateway/session-command-controller.ts +18 -3
- package/packages/piagent-webui/gateway/session-effective-options.ts +47 -0
- package/packages/piagent-webui/gateway/session-live-state.ts +21 -0
- package/packages/piagent-webui/gateway/session-operation-start.ts +5 -1
- package/packages/piagent-webui/gateway/session-operation-watchdog.ts +158 -0
- package/packages/piagent-webui/gateway/session-runtime-factory.ts +45 -0
- package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +139 -124
- package/packages/piagent-webui/server/loopback-server.ts +8 -0
- package/packages/piagent-webui/server/transcript-projection.ts +22 -4
- package/packages/piagent-webui/shared/text-visibility.ts +10 -0
- package/schemas/piagent-webui/catalog-v1.json +9 -0
- package/schemas/piagent-webui/gateway-protocol-v1.schema.json +53 -3
- package/schemas/piagent-webui/session-live-state-v1.schema.json +76 -0
- package/schemas/task-contract.schema.json +12 -1
- package/scripts/benchmark-runner-core.mjs +355 -359
- package/scripts/benchmark-runner-finalization.mjs +309 -0
- package/scripts/benchmark-runner-host-readiness.mjs +94 -0
- package/scripts/benchmark-runner-support.mjs +200 -0
- package/scripts/benchmark-session.mjs +95 -41
- package/scripts/configure-model-scope.sh +4 -3
- package/scripts/helper-evaluation.mjs +1 -1
- package/scripts/install-global.sh +2 -2
- package/scripts/pi-usage-history.mjs +37 -10
- package/scripts/setup.sh +2 -2
- package/scripts/verify-local.sh +5 -1
- package/templates/global/settings.json +2 -1
- package/packages/piagent-webui/dist/client/assets/index-BZRFw1u6.js +0 -174
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
} from "./context-index-policy.js";
|
|
68
68
|
import {
|
|
69
69
|
DEFAULT_MAX_TASK_ATTEMPTS, activeSessionTask, bindSessionTask, createTaskRunId, hasGitEvidenceRoot, listTaskContracts,
|
|
70
|
-
pathWithinChangeEvidenceRoot, priorTaskAttempts, repositoryFileManifest, resolveTaskContract, safeTaskId, summarizeAttempt, taskContractValidationErrors, taskDigestMigrationArchiveStatus,
|
|
70
|
+
pathWithinChangeEvidenceRoot, priorTaskAttempts, repositoryFileManifest, repositoryFileManifestDetails, resolveTaskContract, safeTaskId, summarizeAttempt, taskContractValidationErrors, taskDigestMigrationArchiveStatus,
|
|
71
71
|
workPlanDependencyError, workingTreeSnapshot, workingTreeSnapshotHasUnavailableEvidence, writeTaskContract
|
|
72
72
|
} from "./task-state.js";
|
|
73
73
|
import { classifyRecordedVerificationFailure, classifyVerificationFailure, latestObservedVerification, meaningfulVerificationCommands, selectCompletionRecoveryClassification, selectVerificationPlan } from "./verification-intelligence.js";
|
|
@@ -84,12 +84,14 @@ import {
|
|
|
84
84
|
invalidateAcceptanceReceiptAfterMutation,
|
|
85
85
|
refreshAcceptanceReceipt
|
|
86
86
|
} from "./acceptance-receipt.js";
|
|
87
|
+
import { acceptanceLanguageAdapterForPath, isAcceptanceTestPath } from "./acceptance-language-adapters.js";
|
|
87
88
|
import { allVerifyCommandsPassCurrentTree, changedSnapshotFiles, compactTaskDetails, mergeObservedTaskContext, passingVerifyCommandsForDigest, taskDeltaFilesFromSnapshot } from "./task-contract-view.js";
|
|
88
89
|
import { applyRuntimeLifecycleObservation, runtimeLifecycleMode, workingTreeEvidenceDigest } from "./task-lifecycle.js";
|
|
89
90
|
import { completeTaskDigestRefresh } from "./task-digest-migration.js";
|
|
90
91
|
import { WORKING_TREE_DIGEST_ALGORITHM, isCurrentWorkingTreeDigest, workingTreeObservation, workingTreeSnapshotUsesCurrentAlgorithm } from "./working-tree-digest.js";
|
|
91
92
|
import { appendJsonlBounded } from "./state-retention.js";
|
|
92
93
|
import { ensurePrivateStateDirectory, resolveLocalStatePath } from "./local-state-path.js";
|
|
94
|
+
import { hasDurableContextEvidence } from "./context-evidence.js";
|
|
93
95
|
import { piApprovalBroker, type ApprovalActionDraft, type ApprovalUnavailableFallback } from "../runtime/inspection/approval-broker.ts";
|
|
94
96
|
import { inspectTaskControlState } from "../runtime/inspection/task-control-journal.ts";
|
|
95
97
|
import { createSourceMutationGuardBindings } from "../runtime/policy/source-mutation-guard-binding.ts";
|
|
@@ -121,6 +123,7 @@ import {
|
|
|
121
123
|
automaticReviewLenses,
|
|
122
124
|
automaticReadOnlyTaskScope,
|
|
123
125
|
automaticTaskIntakeMode,
|
|
126
|
+
automaticTaskMutationPolicy,
|
|
124
127
|
automaticTaskRiskLane,
|
|
125
128
|
automaticTaskScope,
|
|
126
129
|
resolveTaskScopePatterns,
|
|
@@ -137,6 +140,8 @@ import { registerToolResultHook } from "../runtime/hooks/tool-result-hook.ts";
|
|
|
137
140
|
import { readRuntimeVersionMetadata, RuntimeSnapshotCapture } from "../runtime/model/runtime-snapshot.ts";
|
|
138
141
|
import { recordRuntimeSnapshotTelemetry } from "../runtime/model/snapshot-telemetry.ts";
|
|
139
142
|
import { captureAuthenticatedModelCatalogFromContext } from "../runtime/model/authenticated-catalog.ts";
|
|
143
|
+
import { normalizeOpenAiCodexReasoningPayload } from "../runtime/model/openai-codex-reasoning.ts";
|
|
144
|
+
import { buildOpenAiCodexWireFingerprint } from "../runtime/model/provider-wire-fingerprint.ts";
|
|
140
145
|
import { ModelRouteRuntime, readModelRouteEvents } from "../runtime/model/model-route-runtime.ts";
|
|
141
146
|
import { parentRoutingModeFromEnvironment, routingObjectiveFromEnvironment } from "../runtime/model/model-route-policy.ts";
|
|
142
147
|
import { ModelSelectionProvenanceTracker } from "../runtime/model/model-selection-provenance.ts";
|
|
@@ -147,6 +152,7 @@ import { SolverShadowRuntime, solverModeFromEnvironment } from "../runtime/solve
|
|
|
147
152
|
import { observeTrajectorySync } from "../runtime/trajectory/trajectory-observability.ts";
|
|
148
153
|
import { TrajectoryRuntime } from "../runtime/trajectory/trajectory-runtime.ts";
|
|
149
154
|
import { PhaseToolRuntime, phaseToolModeFromEnvironment } from "../runtime/tools/phase-tool-runtime.ts";
|
|
155
|
+
import { parseApplyPatchTargets, registerApplyPatchTool } from "../runtime/tools/apply-patch-tool.ts";
|
|
150
156
|
import { authorityReplacementState, ensureTaskAuthorityResumePolicy } from "../runtime/policy/authority-resume-policy.ts";
|
|
151
157
|
import { taskAuthorityDecision } from "../runtime/policy/task-authority-runtime.ts";
|
|
152
158
|
import { selectRecoveryDecision } from "../runtime/recovery/recovery-policy.ts";
|
|
@@ -226,6 +232,7 @@ type TaskStartParameters = {
|
|
|
226
232
|
riskLane: "tiny" | "normal" | "high-risk";
|
|
227
233
|
intakeMode?: "model" | "runtime";
|
|
228
234
|
changeMode?: "source-change" | "read-only";
|
|
235
|
+
mutationPolicy?: "required" | "forbidden";
|
|
229
236
|
verifyGroup?: string;
|
|
230
237
|
maxAttempts?: number;
|
|
231
238
|
expectedOutput: string;
|
|
@@ -424,6 +431,7 @@ const DEFAULT_POLICY: BasePolicy = {
|
|
|
424
431
|
},
|
|
425
432
|
contextBudget: {
|
|
426
433
|
defaultMode: "enforce",
|
|
434
|
+
contextDeltaShadow: "sample",
|
|
427
435
|
maxContextFileChars: 50000,
|
|
428
436
|
maxMemoryFileChars: 20000,
|
|
429
437
|
maxManifestFiles: 80,
|
|
@@ -472,6 +480,7 @@ const DEFAULT_POLICY: BasePolicy = {
|
|
|
472
480
|
ls: ["filesystem-readonly"],
|
|
473
481
|
write: ["filesystem-write"],
|
|
474
482
|
edit: ["filesystem-write"],
|
|
483
|
+
apply_patch: ["filesystem-write"],
|
|
475
484
|
browser: ["browser"],
|
|
476
485
|
github: ["github"]
|
|
477
486
|
}
|
|
@@ -1395,6 +1404,7 @@ function evaluatePermissionProfileToolAccess(
|
|
|
1395
1404
|
function contextBudgetConfig(policy: BasePolicy): Required<ContextBudgetConfig> {
|
|
1396
1405
|
return {
|
|
1397
1406
|
defaultMode: policy.contextBudget?.defaultMode ?? DEFAULT_POLICY.contextBudget?.defaultMode ?? "enforce",
|
|
1407
|
+
contextDeltaShadow: policy.contextBudget?.contextDeltaShadow ?? DEFAULT_POLICY.contextBudget?.contextDeltaShadow ?? "off",
|
|
1398
1408
|
maxContextFileChars: policy.contextBudget?.maxContextFileChars ?? DEFAULT_POLICY.contextBudget?.maxContextFileChars ?? 50000,
|
|
1399
1409
|
maxMemoryFileChars: policy.contextBudget?.maxMemoryFileChars ?? DEFAULT_POLICY.contextBudget?.maxMemoryFileChars ?? 20000,
|
|
1400
1410
|
maxManifestFiles: policy.contextBudget?.maxManifestFiles ?? DEFAULT_POLICY.contextBudget?.maxManifestFiles ?? 80,
|
|
@@ -1544,9 +1554,10 @@ function normalizeWorkPlanSteps(values: unknown): WorkPlanStep[] {
|
|
|
1544
1554
|
function defaultWorkPlan(
|
|
1545
1555
|
summary: string,
|
|
1546
1556
|
riskLane: TaskContract["riskLane"],
|
|
1547
|
-
changeMode: TaskContract["changeMode"]
|
|
1557
|
+
changeMode: TaskContract["changeMode"],
|
|
1558
|
+
mutationPolicy: NonNullable<TaskContract["mutationPolicy"]> = changeMode === "read-only" ? "forbidden" : "required"
|
|
1548
1559
|
): WorkPlanStep[] {
|
|
1549
|
-
if (changeMode === "read-only") {
|
|
1560
|
+
if (changeMode === "read-only" || mutationPolicy === "forbidden") {
|
|
1550
1561
|
if (riskLane === "tiny") {
|
|
1551
1562
|
return [{
|
|
1552
1563
|
id: "scout",
|
|
@@ -1751,6 +1762,69 @@ function collectPatchTargetPaths(value: unknown, key = "", depth = 0): string[]
|
|
|
1751
1762
|
return paths;
|
|
1752
1763
|
}
|
|
1753
1764
|
|
|
1765
|
+
type ApplyPatchTargetInspection =
|
|
1766
|
+
| { carrier: false; exactLocalExecutor: false; targets: [] }
|
|
1767
|
+
| { carrier: true; exactLocalExecutor: boolean; targets: string[]; reason?: undefined }
|
|
1768
|
+
| { carrier: true; exactLocalExecutor: boolean; targets: []; reason: string };
|
|
1769
|
+
|
|
1770
|
+
function isApplyPatchToolName(toolName: string): boolean {
|
|
1771
|
+
const tokens = new Set(actionTokens(toolName));
|
|
1772
|
+
// Pi direct MCP tools are named <server-prefix>_<tool>, for example
|
|
1773
|
+
// filesystem_apply_patch; they never carry an mcp__ prefix. Treat every
|
|
1774
|
+
// apply+patch action carrier as this exact fail-closed protocol so a custom
|
|
1775
|
+
// prefix cannot bypass target extraction.
|
|
1776
|
+
return tokens.has("apply") && tokens.has("patch");
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
function inspectApplyPatchAuthorizationTargets(
|
|
1780
|
+
toolName: string,
|
|
1781
|
+
input: Record<string, unknown>
|
|
1782
|
+
): ApplyPatchTargetInspection {
|
|
1783
|
+
let carrier = toolName;
|
|
1784
|
+
let payload = input;
|
|
1785
|
+
const exactLocalExecutor = toolName === "apply_patch";
|
|
1786
|
+
if (normalizeActionToken(toolName) === "mcp") {
|
|
1787
|
+
const proxyTool = typeof input.tool === "string" ? input.tool.trim() : "";
|
|
1788
|
+
if (!isApplyPatchToolName(proxyTool)) return { carrier: false, exactLocalExecutor: false, targets: [] };
|
|
1789
|
+
carrier = proxyTool;
|
|
1790
|
+
if (typeof input.args !== "string" || input.args.length > MAX_MCP_PROXY_ARGS_CHARS) {
|
|
1791
|
+
return { carrier: true, exactLocalExecutor: false, targets: [], reason: "MCP apply_patch args must be a bounded JSON object string" };
|
|
1792
|
+
}
|
|
1793
|
+
try {
|
|
1794
|
+
const parsed = JSON.parse(input.args);
|
|
1795
|
+
if (!isPlainRecord(parsed)) return { carrier: true, exactLocalExecutor: false, targets: [], reason: "MCP apply_patch args must decode to a JSON object" };
|
|
1796
|
+
payload = parsed;
|
|
1797
|
+
} catch {
|
|
1798
|
+
return { carrier: true, exactLocalExecutor: false, targets: [], reason: "MCP apply_patch args must be valid JSON" };
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
if (!isApplyPatchToolName(carrier)) return { carrier: false, exactLocalExecutor: false, targets: [] };
|
|
1802
|
+
const keys = Object.keys(payload);
|
|
1803
|
+
if (keys.length !== 1 || keys[0] !== "patch" || typeof payload.patch !== "string") {
|
|
1804
|
+
return { carrier: true, exactLocalExecutor, targets: [], reason: "apply_patch input must contain only one string field named patch" };
|
|
1805
|
+
}
|
|
1806
|
+
try {
|
|
1807
|
+
return { carrier: true, exactLocalExecutor, targets: parseApplyPatchTargets(payload.patch) };
|
|
1808
|
+
} catch (error) {
|
|
1809
|
+
return {
|
|
1810
|
+
carrier: true,
|
|
1811
|
+
exactLocalExecutor,
|
|
1812
|
+
targets: [],
|
|
1813
|
+
reason: error instanceof Error ? error.message : "apply_patch target extraction failed"
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
function authorizationMutationTargetInspection(
|
|
1819
|
+
cwd: string,
|
|
1820
|
+
toolName: string,
|
|
1821
|
+
input: Record<string, unknown>
|
|
1822
|
+
): { targets: string[]; patchCarrier: boolean; exactLocalPatchExecutor: boolean; reason?: string } {
|
|
1823
|
+
const patch = inspectApplyPatchAuthorizationTargets(toolName, input);
|
|
1824
|
+
if (patch.carrier) return { targets: patch.targets, patchCarrier: true, exactLocalPatchExecutor: patch.exactLocalExecutor, reason: patch.reason };
|
|
1825
|
+
return { targets: taskMutationTargets(cwd, toolName, input), patchCarrier: false, exactLocalPatchExecutor: false };
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1754
1828
|
function taskMutationIdentity(toolName: string, input: Record<string, unknown>): string {
|
|
1755
1829
|
if (normalizeActionToken(toolName) !== "mcp") return toolName;
|
|
1756
1830
|
const proxyTool = typeof input.tool === "string" ? input.tool.trim() : "";
|
|
@@ -2102,7 +2176,7 @@ function flushObservedTaskContext(
|
|
|
2102
2176
|
const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
|
|
2103
2177
|
if (!task || task.trace.outcome !== "pending") return task;
|
|
2104
2178
|
const added = mergeObservedTaskContext(task, pendingContext, maxManifestFiles, redactText);
|
|
2105
|
-
const lifecycle = task.changeMode === "read-only"
|
|
2179
|
+
const lifecycle = (task.changeMode === "read-only" || task.mutationPolicy === "forbidden") && hasDurableContextEvidence(task)
|
|
2106
2180
|
? applyRuntimeLifecycleObservation(task, "context-complete", nowIso())
|
|
2107
2181
|
: { changed: false, mode: runtimeLifecycleMode(task) };
|
|
2108
2182
|
if (added.length === 0 && !lifecycle.changed) return task;
|
|
@@ -2173,9 +2247,13 @@ function prepareToolInputForPolicy(
|
|
|
2173
2247
|
return { input, reason: "MCP proxy args must be valid JSON" };
|
|
2174
2248
|
}
|
|
2175
2249
|
if (!isPlainRecord(parsed)) return { input, reason: "MCP proxy args must decode to a JSON object" };
|
|
2176
|
-
const
|
|
2177
|
-
|
|
2178
|
-
|
|
2250
|
+
const exactPatch = inspectApplyPatchAuthorizationTargets(toolName, input);
|
|
2251
|
+
if (exactPatch.carrier && exactPatch.reason) return { input, reason: exactPatch.reason };
|
|
2252
|
+
const patchTargets = exactPatch.carrier
|
|
2253
|
+
? exactPatch.targets
|
|
2254
|
+
: actionTokens(proxyTool).includes("patch")
|
|
2255
|
+
? collectPatchTargetPaths(parsed)
|
|
2256
|
+
: [];
|
|
2179
2257
|
policyInput = patchTargets.length > 0
|
|
2180
2258
|
? { ...input, proxyArgs: parsed, proxyPatchTargets: { paths: patchTargets } }
|
|
2181
2259
|
: { ...input, proxyArgs: parsed };
|
|
@@ -3572,7 +3650,7 @@ function taskScopeIncludesPath(scope: string[], file: string): boolean {
|
|
|
3572
3650
|
}
|
|
3573
3651
|
|
|
3574
3652
|
function verifierCommandInstructions(commands: string[]): string[] {
|
|
3575
|
-
return commands.map((command, index) => `Verifier ${index + 1} (run as
|
|
3653
|
+
return commands.map((command, index) => `Verifier ${index + 1} (run as an exact standalone shell command): ${command}`);
|
|
3576
3654
|
}
|
|
3577
3655
|
|
|
3578
3656
|
function evaluateTaskGate(
|
|
@@ -3605,7 +3683,7 @@ function evaluateTaskGate(
|
|
|
3605
3683
|
if (task.attempt > task.maxAttempts) missing.push(`attempt within maxAttempts (${task.attempt}/${task.maxAttempts})`);
|
|
3606
3684
|
const plannedVerifyCommands = meaningfulVerificationCommands(task.verifyCommands);
|
|
3607
3685
|
if (task.changeMode === "source-change" && plannedVerifyCommands.length === 0) missing.push("meaningful verify command");
|
|
3608
|
-
if (finalGate.requireContextManifest && task
|
|
3686
|
+
if (finalGate.requireContextManifest && !hasDurableContextEvidence(task)) missing.push("context manifest");
|
|
3609
3687
|
if (task.changeMode === "source-change" && finalGate.requireVerifyEvidence && task.verifyEvidence.length === 0) missing.push("verify evidence");
|
|
3610
3688
|
if (task.verifyEvidence.some((evidence) => evidence.observed !== true)) {
|
|
3611
3689
|
warnings.push("Unobserved verify evidence is ignored by the passing verify gate.");
|
|
@@ -3632,14 +3710,14 @@ function evaluateTaskGate(
|
|
|
3632
3710
|
missing.push(`completed work plan (${incompleteSteps.map((step) => `${step.id}:${step.status}`).join(", ")})`);
|
|
3633
3711
|
}
|
|
3634
3712
|
const changedFileEvidence = taskChangedFileEvidence(cwd, task, currentDigests);
|
|
3635
|
-
if (task.changeMode === "source-change" && task.trace.outcome === "completed") {
|
|
3713
|
+
if (task.changeMode === "source-change" && task.trace.outcome === "completed" && task.mutationPolicy !== "forbidden") {
|
|
3636
3714
|
if (task.changedFiles.length === 0) missing.push("changed files");
|
|
3637
3715
|
if (changedFileEvidence.undeclared.length > 0) missing.push(`declared observed changes (${changedFileEvidence.undeclared.join(", ")})`);
|
|
3638
3716
|
if (changedFileEvidence.unsupportedClaims.length > 0) missing.push(`supported changed-file claims (${changedFileEvidence.unsupportedClaims.join(", ")})`);
|
|
3639
3717
|
if (changedFileEvidence.outsideScope.length > 0) missing.push(`changes within task scope (${changedFileEvidence.outsideScope.join(", ")})`);
|
|
3640
3718
|
}
|
|
3641
|
-
if (task.changeMode === "read-only" && changedFileEvidence.expected.length > 0) {
|
|
3642
|
-
missing.push(`
|
|
3719
|
+
if ((task.changeMode === "read-only" || task.mutationPolicy === "forbidden") && changedFileEvidence.expected.length > 0) {
|
|
3720
|
+
missing.push(`mutation-forbidden task has observed changes (${changedFileEvidence.expected.join(", ")})`);
|
|
3643
3721
|
}
|
|
3644
3722
|
const acceptance = refreshAcceptanceReceipt(task, {
|
|
3645
3723
|
cwd,
|
|
@@ -3867,6 +3945,55 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
3867
3945
|
pi.on("thinking_level_select", (_event, ctx) => {
|
|
3868
3946
|
modelSelectionProvenance.observeThinkingSelection(ctx.sessionManager.getSessionId());
|
|
3869
3947
|
});
|
|
3948
|
+
pi.on("before_provider_request", (event, ctx) => {
|
|
3949
|
+
const normalized = normalizeOpenAiCodexReasoningPayload({
|
|
3950
|
+
payload: event.payload,
|
|
3951
|
+
provider: ctx.model?.provider,
|
|
3952
|
+
modelId: ctx.model?.id,
|
|
3953
|
+
hostThinkingLevel: pi.getThinkingLevel()
|
|
3954
|
+
});
|
|
3955
|
+
if (normalized.applicable) {
|
|
3956
|
+
telemetry(ctx, {
|
|
3957
|
+
event: "provider_request_reasoning",
|
|
3958
|
+
hostThinkingLevel: normalized.hostThinkingLevel,
|
|
3959
|
+
providerReasoningEffort: normalized.providerEffort,
|
|
3960
|
+
expectedProviderReasoningEffort: normalized.expectedProviderEffort,
|
|
3961
|
+
providerReasoningEffortMatches: normalized.providerEffort === normalized.expectedProviderEffort,
|
|
3962
|
+
providerReasoningNormalized: normalized.changed,
|
|
3963
|
+
providerReasoningReasonCode: normalized.reasonCode
|
|
3964
|
+
});
|
|
3965
|
+
}
|
|
3966
|
+
const wire = buildOpenAiCodexWireFingerprint({
|
|
3967
|
+
payload: normalized.changed ? normalized.payload : event.payload,
|
|
3968
|
+
provider: ctx.model?.provider,
|
|
3969
|
+
modelId: ctx.model?.id,
|
|
3970
|
+
workingDirectory: ctx.cwd,
|
|
3971
|
+
platformRoot: PLATFORM_ROOT
|
|
3972
|
+
});
|
|
3973
|
+
if (wire.applicable) {
|
|
3974
|
+
telemetry(ctx, {
|
|
3975
|
+
event: "provider_request_wire_surface",
|
|
3976
|
+
state: wire.state,
|
|
3977
|
+
reasonCode: wire.reasonCode,
|
|
3978
|
+
providerModelId: wire.modelId,
|
|
3979
|
+
instructionsHash: wire.instructionsHash,
|
|
3980
|
+
baseInstructionsHash: wire.baseInstructionsHash,
|
|
3981
|
+
instructionNormalization: wire.instructionNormalization,
|
|
3982
|
+
instructionChars: wire.instructionChars,
|
|
3983
|
+
orderedToolSurfaceHash: wire.orderedToolSurfaceHash,
|
|
3984
|
+
providerToolCount: wire.toolCount,
|
|
3985
|
+
deferredToolSurfaceHash: wire.deferredToolSurfaceHash,
|
|
3986
|
+
deferredToolCount: wire.deferredToolCount,
|
|
3987
|
+
deferredToolBatchCount: wire.deferredToolBatchCount,
|
|
3988
|
+
providerReasoningEffort: wire.reasoningEffort,
|
|
3989
|
+
providerTextVerbosity: wire.textVerbosity,
|
|
3990
|
+
providerToolChoiceKind: wire.toolChoiceKind,
|
|
3991
|
+
providerToolChoiceHash: wire.toolChoiceHash,
|
|
3992
|
+
requestPrefixFingerprint: wire.requestPrefixFingerprint
|
|
3993
|
+
});
|
|
3994
|
+
}
|
|
3995
|
+
return normalized.changed ? normalized.payload : undefined;
|
|
3996
|
+
});
|
|
3870
3997
|
|
|
3871
3998
|
function freshModelRouteBoundary(ctx: ExtensionContext): boolean {
|
|
3872
3999
|
try {
|
|
@@ -3928,7 +4055,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
3928
4055
|
let taskIdentity = runtimeState.taskIdentity(ctx);
|
|
3929
4056
|
if (!taskIdentity) {
|
|
3930
4057
|
const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
|
|
3931
|
-
if (task) {
|
|
4058
|
+
if (task?.trace.outcome === "pending") {
|
|
3932
4059
|
runtimeState.cacheTaskIdentity(ctx, task);
|
|
3933
4060
|
taskIdentity = { taskId: task.taskId, taskRunId: task.taskRunId };
|
|
3934
4061
|
}
|
|
@@ -4080,7 +4207,8 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4080
4207
|
const snapshot = workingTreeSnapshot(ctx.cwd) as Record<string, string>;
|
|
4081
4208
|
const resume = workingTreeSnapshotHasUnavailableEvidence(snapshot)
|
|
4082
4209
|
? { openRepair: false }
|
|
4083
|
-
: { openRepair: taskAuthorityDecision(task, "CAP-13", "block").allowed
|
|
4210
|
+
: { openRepair: (taskAuthorityDecision(task, "CAP-13", "block").allowed
|
|
4211
|
+
|| taskAuthorityDecision(task, "CAP-12", "mutate").allowed) && semanticRepairRuntime.resume({
|
|
4084
4212
|
cwd: ctx.cwd,
|
|
4085
4213
|
task,
|
|
4086
4214
|
sessionId: ctx.sessionManager.getSessionId(),
|
|
@@ -4115,6 +4243,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4115
4243
|
});
|
|
4116
4244
|
|
|
4117
4245
|
registerInputHook(pi, {
|
|
4246
|
+
state: runtimeState,
|
|
4118
4247
|
boilerplateCollapseChars: BOILERPLATE_COLLAPSE_CHARS,
|
|
4119
4248
|
activeTask: (ctx) => activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined, authorityPolicy: (ctx, task) => ensureTaskAuthorityResumePolicy(ctx.cwd, task, { authorityProfile: loadProfileFromContext(ctx).authorityProfile, environment: process.env }),
|
|
4120
4249
|
readProtectedPaths: (ctx) => effectiveProtectedPaths(policy, loadProfileFromContext(ctx)).readProtectedPaths,
|
|
@@ -4141,6 +4270,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4141
4270
|
registerAgentStartHook(pi, {
|
|
4142
4271
|
state: runtimeState,
|
|
4143
4272
|
autoContextEnabled,
|
|
4273
|
+
contextDeltaShadowMode: contextBudgetConfig(policy).contextDeltaShadow,
|
|
4144
4274
|
activeTask: (ctx) => activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined,
|
|
4145
4275
|
readProtectedPaths: (ctx) => effectiveProtectedPaths(policy, loadProfileFromContext(ctx)).readProtectedPaths,
|
|
4146
4276
|
contextExcludePatterns: (ctx) => contextIndexExcludePatterns(policy, loadProfileFromContext(ctx)),
|
|
@@ -4168,7 +4298,10 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4168
4298
|
completionProjection: completionTaskProjection,
|
|
4169
4299
|
evaluateGate: (cwd, task, currentDigests, currentDigest) => {
|
|
4170
4300
|
const gate = evaluateTaskGate(cwd, task, policy, { currentDigests, currentWorkingTreeDigest: currentDigest });
|
|
4171
|
-
const repairBlock = taskAuthorityDecision(task, "CAP-13", "block").allowed
|
|
4301
|
+
const repairBlock = (taskAuthorityDecision(task, "CAP-13", "block").allowed
|
|
4302
|
+
|| taskAuthorityDecision(task, "CAP-12", "mutate").allowed)
|
|
4303
|
+
? semanticRepairRuntime.completionBlock(cwd, task.taskRunId)
|
|
4304
|
+
: undefined;
|
|
4172
4305
|
return repairBlock ? { ...gate, decision: "fail", missing: [...new Set([...gate.missing, repairBlock])] } : gate;
|
|
4173
4306
|
},
|
|
4174
4307
|
writeTask,
|
|
@@ -4187,6 +4320,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4187
4320
|
state: runtimeState,
|
|
4188
4321
|
activeTask: (ctx) => activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined,
|
|
4189
4322
|
maxManifestFiles: contextBudgetConfig(policy).maxManifestFiles,
|
|
4323
|
+
flushObservedTaskContext,
|
|
4190
4324
|
readProtectedPaths: (ctx) => effectiveProtectedPaths(policy, loadProfileFromContext(ctx)).readProtectedPaths,
|
|
4191
4325
|
recordObservedBash: (observed) => bashResults.record(observed),
|
|
4192
4326
|
observedBashLedgerPath,
|
|
@@ -4202,7 +4336,10 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4202
4336
|
now: nowIso,
|
|
4203
4337
|
completeSemanticRepair: (ctx, event, metadata) => {
|
|
4204
4338
|
const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
|
|
4205
|
-
return task && taskAuthorityDecision(task, "CAP-13", "mutate").allowed
|
|
4339
|
+
return task && (taskAuthorityDecision(task, "CAP-13", "mutate").allowed
|
|
4340
|
+
|| taskAuthorityDecision(task, "CAP-12", "mutate").allowed)
|
|
4341
|
+
? semanticRepairRuntime.complete(ctx, task, event, metadata)
|
|
4342
|
+
: undefined;
|
|
4206
4343
|
},
|
|
4207
4344
|
syncTrajectory: (ctx, contextObserved) => {
|
|
4208
4345
|
const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
|
|
@@ -4216,10 +4353,19 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4216
4353
|
telemetry,
|
|
4217
4354
|
activity: recordActivity,
|
|
4218
4355
|
beforeAuthorize: (event, ctx) => {
|
|
4219
|
-
|
|
4356
|
+
const toolInput = isPlainRecord(event.input) ? event.input : {};
|
|
4357
|
+
const targetInspection = authorizationMutationTargetInspection(ctx.cwd, event.toolName, toolInput);
|
|
4358
|
+
if (targetInspection.reason) return { block: true, reason: `Blocked ${event.toolName}: ${targetInspection.reason}.` };
|
|
4359
|
+
if (!["edit", "write"].includes(event.toolName) && !targetInspection.exactLocalPatchExecutor) return;
|
|
4220
4360
|
const task = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
|
|
4221
4361
|
if (!task || task.trace.outcome !== "pending" || task.changeMode !== "source-change") return;
|
|
4222
|
-
|
|
4362
|
+
const semanticRepairEnabled = taskAuthorityDecision(task, "CAP-13", "mutate").allowed;
|
|
4363
|
+
const semanticRepairExactlyOff = !taskAuthorityDecision(task, "CAP-13", "observe").allowed;
|
|
4364
|
+
const boundedRecoveryEnabled = autoRecoveryEnabled
|
|
4365
|
+
&& taskAuthorityDecision(task, "CAP-09", "block").allowed
|
|
4366
|
+
&& taskAuthorityDecision(task, "CAP-12", "mutate").allowed
|
|
4367
|
+
&& semanticRepairExactlyOff;
|
|
4368
|
+
if (!semanticRepairEnabled && !boundedRecoveryEnabled) return;
|
|
4223
4369
|
const phase = trajectoryRuntime.status(ctx.cwd, task.taskRunId);
|
|
4224
4370
|
if (!phase.enforcementSafe || !["verify", "review"].includes(String(phase.phase))) return;
|
|
4225
4371
|
|
|
@@ -4227,16 +4373,41 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4227
4373
|
if (workingTreeSnapshotHasUnavailableEvidence(currentSnapshot)) return;
|
|
4228
4374
|
const currentDigest = workingTreeEvidenceDigest(currentSnapshot);
|
|
4229
4375
|
const expectedReviewPaths = taskDeltaFilesFromSnapshot(task, currentSnapshot);
|
|
4230
|
-
const
|
|
4231
|
-
const targets = taskMutationTargets(ctx.cwd, event.toolName, toolInput);
|
|
4376
|
+
const targets = targetInspection.targets;
|
|
4232
4377
|
const boundedInScopeMutation = targets.length > 0
|
|
4233
4378
|
&& targets.every((file) => taskScopeIncludesPath(task.scope, file));
|
|
4379
|
+
const verifierCurrent = allVerifyCommandsPassCurrentTree(task, currentDigest);
|
|
4234
4380
|
|
|
4235
|
-
if (phase.phase === "verify" && boundedInScopeMutation && semanticRepairRuntime.prepare({
|
|
4381
|
+
if (phase.phase === "verify" && boundedInScopeMutation && semanticRepairEnabled && semanticRepairRuntime.prepare({
|
|
4236
4382
|
ctx, task, event, currentDigest, currentDeltaPaths: expectedReviewPaths, targetPaths: targets,
|
|
4237
|
-
verifierCurrent
|
|
4383
|
+
verifierCurrent
|
|
4238
4384
|
})) return;
|
|
4239
4385
|
|
|
4386
|
+
// A public verifier can pass before every explicit user criterion is
|
|
4387
|
+
// covered. CAP-12 therefore reserves one exact structured call when the
|
|
4388
|
+
// model catches its own omission in the same task/run/session. The call
|
|
4389
|
+
// still traverses every normal authorization check; verify -> repair is
|
|
4390
|
+
// committed only by the tool-result hook after a successful tree change.
|
|
4391
|
+
if (phase.phase === "verify" && boundedRecoveryEnabled && verifierCurrent && boundedInScopeMutation) {
|
|
4392
|
+
const authoredFileDigests = runtimeState.successfulModelMutationDigests(
|
|
4393
|
+
{ taskId: task.taskId, taskRunId: task.taskRunId, sessionId: task.sessionId },
|
|
4394
|
+
currentSnapshot
|
|
4395
|
+
);
|
|
4396
|
+
if (semanticRepairRuntime.prepareBoundedRecovery({
|
|
4397
|
+
ctx,
|
|
4398
|
+
task,
|
|
4399
|
+
event,
|
|
4400
|
+
currentDigest,
|
|
4401
|
+
currentDeltaPaths: expectedReviewPaths,
|
|
4402
|
+
observedChangedPaths: task.observedChangedFiles,
|
|
4403
|
+
authoredFileDigests,
|
|
4404
|
+
targetPaths: targets,
|
|
4405
|
+
verifierCurrent
|
|
4406
|
+
})) return;
|
|
4407
|
+
}
|
|
4408
|
+
|
|
4409
|
+
if (!semanticRepairEnabled) return;
|
|
4410
|
+
|
|
4240
4411
|
let checkpoint = runtimeState.performanceReviewCheckpoint(task.taskRunId);
|
|
4241
4412
|
const checkpointReady = checkpoint?.workingTreeDigest === currentDigest
|
|
4242
4413
|
&& checkpoint.reviewSatisfied
|
|
@@ -4286,8 +4457,10 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4286
4457
|
const currentDigest = workingTreeEvidenceDigest(workingTreeSnapshot(ctx.cwd) as Record<string, string>);
|
|
4287
4458
|
const phase = trajectoryRuntime.status(ctx.cwd, task.taskRunId);
|
|
4288
4459
|
const toolInput = isPlainRecord(event.input) ? event.input : {};
|
|
4289
|
-
const
|
|
4290
|
-
if (
|
|
4460
|
+
const targetInspection = authorizationMutationTargetInspection(ctx.cwd, event.toolName, toolInput);
|
|
4461
|
+
if (targetInspection.reason) return { block: true, reason: `Blocked ${event.toolName}: ${targetInspection.reason}.` };
|
|
4462
|
+
const targets = targetInspection.targets;
|
|
4463
|
+
if ((["edit", "write"].includes(event.toolName) || targetInspection.patchCarrier) && (
|
|
4291
4464
|
targets.length === 0 || targets.some((file) => !taskScopeIncludesPath(task.scope, file))
|
|
4292
4465
|
)) {
|
|
4293
4466
|
runtimeState.denyPerformanceReviewTool(task.taskRunId);
|
|
@@ -4361,6 +4534,11 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4361
4534
|
authorize: async (event, ctx) => {
|
|
4362
4535
|
const preTask = activeSessionTask(ctx.cwd, ctx.sessionManager.getSessionId()) as TaskContract | undefined;
|
|
4363
4536
|
const preInput = isPlainRecord(event.input) ? event.input : {};
|
|
4537
|
+
const authorizationTargetInspection = authorizationMutationTargetInspection(ctx.cwd, event.toolName, preInput);
|
|
4538
|
+
if (authorizationTargetInspection.reason) {
|
|
4539
|
+
return { block: true, reason: `Blocked ${event.toolName}: ${authorizationTargetInspection.reason}.` };
|
|
4540
|
+
}
|
|
4541
|
+
const authorizationTargets = authorizationTargetInspection.targets;
|
|
4364
4542
|
const preSnapshot = preTask ? workingTreeSnapshot(ctx.cwd) as Record<string, string> : undefined;
|
|
4365
4543
|
const reservedFirstCall = Boolean(preTask && preSnapshot && !workingTreeSnapshotHasUnavailableEvidence(preSnapshot) && semanticRepairRuntime.reservedCallMatches({
|
|
4366
4544
|
cwd: ctx.cwd,
|
|
@@ -4369,7 +4547,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4369
4547
|
toolCallId: String(event.toolCallId),
|
|
4370
4548
|
toolName: event.toolName,
|
|
4371
4549
|
currentDigest: workingTreeEvidenceDigest(preSnapshot),
|
|
4372
|
-
targetPaths:
|
|
4550
|
+
targetPaths: authorizationTargets
|
|
4373
4551
|
}));
|
|
4374
4552
|
const phaseDecision = reservedFirstCall || (preTask?.workingTreeDigestMigration?.status === "verification-refresh-required" && SHELL_TOOL_NAMES.has(event.toolName)) ? undefined : phaseToolRuntime.toolDecision(ctx, event.toolName);
|
|
4375
4553
|
if (phaseDecision) return phaseDecision;
|
|
@@ -4427,10 +4605,11 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4427
4605
|
const taskContractRequired = governedTaskProfile
|
|
4428
4606
|
&& runtime.finalGate === "enforce"
|
|
4429
4607
|
&& finalGateConfig(policy).requireTaskContract;
|
|
4430
|
-
if (sessionTask?.trace.outcome === "pending" && sessionTask.changeMode === "read-only"
|
|
4608
|
+
if (sessionTask?.trace.outcome === "pending" && (sessionTask.changeMode === "read-only" || sessionTask.mutationPolicy === "forbidden")
|
|
4609
|
+
&& !SHELL_TOOL_NAMES.has(event.toolName) && isTaskMutationTool(event.toolName, toolInput)) {
|
|
4431
4610
|
return {
|
|
4432
4611
|
block: true,
|
|
4433
|
-
reason: `Task ${sessionTask.taskId} is read-only; ${event.toolName} cannot mutate project or external state in this task. Start a
|
|
4612
|
+
reason: `Task ${sessionTask.taskId} ${sessionTask.changeMode === "read-only" ? "is read-only" : "forbids source mutation"}; ${event.toolName} cannot mutate project or external state in this task. Start a mutation-required source task for implementation.`
|
|
4434
4613
|
};
|
|
4435
4614
|
}
|
|
4436
4615
|
const preparedInput = prepareToolInputForPolicy(event.toolName, toolInput, policy);
|
|
@@ -4473,10 +4652,13 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4473
4652
|
&& !candidate.startsWith("../")
|
|
4474
4653
|
&& !candidate.startsWith(".pi/piagent-state/")
|
|
4475
4654
|
));
|
|
4476
|
-
|
|
4655
|
+
const mutationForbidden = sessionTask?.trace.outcome === "pending"
|
|
4656
|
+
&& (sessionTask.changeMode === "read-only" || sessionTask.mutationPolicy === "forbidden");
|
|
4657
|
+
const exactForbiddenTaskVerifier = mutationForbidden && sessionTask.changeMode === "source-change" && configuredVerifierShell;
|
|
4658
|
+
if (mutationForbidden && !exactForbiddenTaskVerifier && !isReadOnlyTaskShellCommand(command, execDecision.segments)) {
|
|
4477
4659
|
return {
|
|
4478
4660
|
block: true,
|
|
4479
|
-
reason: `Task ${sessionTask.taskId} is read-only; this shell command is not in the read-only inspection allowlist.`
|
|
4661
|
+
reason: `Task ${sessionTask.taskId} ${sessionTask.changeMode === "read-only" ? "is read-only; this shell command is not in the read-only inspection allowlist" : "forbids source mutation; this shell command is neither bounded inspection nor an exact configured verifier"}.`
|
|
4480
4662
|
};
|
|
4481
4663
|
}
|
|
4482
4664
|
if (execDecision.mode !== "off" && execDecision.decision === "forbid") {
|
|
@@ -4533,18 +4715,21 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4533
4715
|
// Shell inputs need command grammar, not field-by-field path guessing: an
|
|
4534
4716
|
// exclusion selector in `args` is not an accessed path. The semantic shell
|
|
4535
4717
|
// checks above already inspect the normalized command, including redirects.
|
|
4718
|
+
const policyPathInput = authorizationTargetInspection.patchCarrier
|
|
4719
|
+
? { paths: authorizationTargets }
|
|
4720
|
+
: preparedInput.input;
|
|
4536
4721
|
const pathDecision = SHELL_TOOL_NAMES.has(event.toolName) ? { block: false, reason: undefined } : evaluatePathLikeToolAccess(
|
|
4537
4722
|
ctx.cwd,
|
|
4538
4723
|
preparedInput.proxyToolName ?? event.toolName,
|
|
4539
|
-
|
|
4724
|
+
policyPathInput,
|
|
4540
4725
|
pathPolicy.writeProtectedPaths,
|
|
4541
4726
|
pathPolicy.readProtectedPaths,
|
|
4542
4727
|
pathPolicy.readOnlyPaths,
|
|
4543
4728
|
permissionProfile.mode === "trusted-full-access" ? undefined : capabilityState.filesystemRead,
|
|
4544
4729
|
permissionProfile.mode === "trusted-full-access" ? undefined : capabilityState.filesystemWrite,
|
|
4545
4730
|
{
|
|
4546
|
-
forceScopeAware: Boolean(preparedInput.proxyToolName),
|
|
4547
|
-
forceWrite: preparedInput.proxyAction?.decision === "confirm",
|
|
4731
|
+
forceScopeAware: authorizationTargetInspection.patchCarrier || Boolean(preparedInput.proxyToolName),
|
|
4732
|
+
forceWrite: authorizationTargetInspection.patchCarrier || preparedInput.proxyAction?.decision === "confirm",
|
|
4548
4733
|
allowAmbiguousFilesystemContentFields: !usesKnownExternalProvider && !isPiagentTool(event.toolName)
|
|
4549
4734
|
}
|
|
4550
4735
|
);
|
|
@@ -4573,7 +4758,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4573
4758
|
|
|
4574
4759
|
const mutationTargets = SHELL_TOOL_NAMES.has(event.toolName)
|
|
4575
4760
|
? shellMutationTargets
|
|
4576
|
-
:
|
|
4761
|
+
: authorizationTargets;
|
|
4577
4762
|
const directProjectMutation = !SHELL_TOOL_NAMES.has(event.toolName) && (
|
|
4578
4763
|
WRITE_TOOL_NAMES.has(event.toolName)
|
|
4579
4764
|
|| mutationTargets.length > 0
|
|
@@ -4597,7 +4782,9 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4597
4782
|
opaqueCarrier: semanticOpaqueCarrier,
|
|
4598
4783
|
targetExtractionComplete: SHELL_TOOL_NAMES.has(event.toolName)
|
|
4599
4784
|
? !shellHasOpaqueWritePrimitive(authorizedShellCommand)
|
|
4600
|
-
:
|
|
4785
|
+
: authorizationTargetInspection.patchCarrier
|
|
4786
|
+
? authorizationTargetInspection.exactLocalPatchExecutor
|
|
4787
|
+
: !semanticOpaqueCarrier && preparedInput.proxyShellCarrier !== true,
|
|
4601
4788
|
recordedAt: nowIso()
|
|
4602
4789
|
})
|
|
4603
4790
|
: { handled: false, allowed: false, bypassPhase: false };
|
|
@@ -4656,10 +4843,12 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4656
4843
|
};
|
|
4657
4844
|
}
|
|
4658
4845
|
}
|
|
4659
|
-
if (runtime.contextBudget !== "off" && ["write", "edit"].includes(event.toolName)) {
|
|
4660
|
-
const
|
|
4661
|
-
|
|
4662
|
-
|
|
4846
|
+
if (runtime.contextBudget !== "off" && (["write", "edit"].includes(event.toolName) || authorizationTargetInspection.patchCarrier)) {
|
|
4847
|
+
const budgetTargets = authorizationTargetInspection.patchCarrier
|
|
4848
|
+
? authorizationTargets
|
|
4849
|
+
: [extractLikelyPathFromInput(ctx.cwd, preInput)].filter((item): item is string => Boolean(item));
|
|
4850
|
+
const budget = contextBudgetConfig(policy);
|
|
4851
|
+
for (const relativePath of budgetTargets) {
|
|
4663
4852
|
const stats = candidateFileBudget(ctx.cwd, relativePath, budget);
|
|
4664
4853
|
if (runtime.contextBudget === "enforce" && stats.exists && stats.overLimit) {
|
|
4665
4854
|
return { block: true, reason: `Context budget blocked editing large file ${relativePath}: ${stats.chars} chars > ${budget.maxContextFileChars}` };
|
|
@@ -4688,13 +4877,16 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4688
4877
|
}
|
|
4689
4878
|
}
|
|
4690
4879
|
});
|
|
4880
|
+
// Register only after authorization is live. The same-name override is intentional:
|
|
4881
|
+
// guard target parsing, provenance, and atomicity require this exact tool contract.
|
|
4882
|
+
registerApplyPatchTool(pi, Type);
|
|
4691
4883
|
const registrationDeps = {
|
|
4692
4884
|
CONTEXT_INDEX_EDGE_KINDS, CONTEXT_INDEX_NODE_KINDS, DEFAULT_MAX_TASK_ATTEMPTS, FRESH_COMMAND_ACTIONS, FRESH_COMMAND_HELP,
|
|
4693
4885
|
ONBOARDING_COMMAND_ACTIONS, ORCHESTRATION_ROLES, PIAGENT_TOOL_NAMES, READ_ONLY_TOOL_NAMES, REVIEW_LENSES,
|
|
4694
4886
|
StringEnum, TECH_STACK_MANIFEST_FILE, TOOL_RESULT_CAPTURE_MAX_CHARS, TOOL_RESULT_COMPACT_CHAR_THRESHOLD, TOOL_RESULT_COMPACT_LINE_THRESHOLD,
|
|
4695
4887
|
TOOL_RESULT_PREVIEW_MAX_CHARS, Type, WORKFLOW_COMMAND_EXCLUSIONS, activateToolGroups, activeSessionTask,
|
|
4696
4888
|
activeTaskToolGroups, allVerifyCommandsPassCurrentTree, appendMemoryNote, appendSessionTrace, appendTrace,
|
|
4697
|
-
acceptanceBaselineGuidance, acceptanceProofGuidance, applyAcceptanceRecoveryProvenance, applyRuntimeLifecycleObservation, automaticAcceptanceCriteria, automaticReadOnlyTaskScope, automaticReviewLenses, automaticTaskIntakeMode, automaticTaskRiskLane, automaticTaskScope,
|
|
4889
|
+
acceptanceBaselineGuidance, acceptanceLanguageAdapterForPath, acceptanceProofGuidance, applyAcceptanceRecoveryProvenance, applyRuntimeLifecycleObservation, automaticAcceptanceCriteria, automaticReadOnlyTaskScope, automaticReviewLenses, automaticTaskIntakeMode, automaticTaskMutationPolicy, automaticTaskRiskLane, automaticTaskScope,
|
|
4698
4890
|
bashResults, bindSessionTask, buildAcceptanceReceipt, buildContextEfficiencyReport, buildContextIndexStatus, buildLiveTaskStatus, buildTaskEfficiencyMetrics,
|
|
4699
4891
|
buildContextIndexV2, buildContextPack, buildContextPreflight, buildProfileOptions, buildProfileTechOptions,
|
|
4700
4892
|
buildTestImpact, buildUsageSnapshot, candidateFileBudget, checkoutReferenceRepo, classifyContextTask,
|
|
@@ -4706,7 +4898,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4706
4898
|
extensionDir, externalActionPolicyConfig, extractDocument, finalGateConfig, findMatchingObservedBashResult,
|
|
4707
4899
|
formatContextPreflight, formatCount, formatLiveTaskStatus, formatPercent, formatTechOptionsText, formatTechSelectionSummary,
|
|
4708
4900
|
formatToolResultCaptureStatus, formatUsageSnapshot, fs, hasGitEvidenceRoot, hasOperatorSessionName, helpersMode,
|
|
4709
|
-
loadProfileFromContext, matchesAnyPath, matchesProtectedPath, mcpActions, mcpApprovalCache,
|
|
4901
|
+
isAcceptanceTestPath, loadProfileFromContext, matchesAnyPath, matchesProtectedPath, mcpActions, mcpApprovalCache,
|
|
4710
4902
|
memoryHandbookPath, memoryLocalDir, memorySummaryPath, normalizeProjectProfileName, normalizeRelative,
|
|
4711
4903
|
normalizeReviewLenses, normalizeTechSelections, normalizeWorkPlanSteps, nowIso, observedBashLedgerPath,
|
|
4712
4904
|
path, permissionOverrideFromContext, permissionProfilesConfig, policy, prefixCompletions,
|
|
@@ -4724,7 +4916,7 @@ export default function piagentGuard(pi: ExtensionAPI) {
|
|
|
4724
4916
|
techContextDirPath, techContextFilePath, techContextRelativePath, techOptionById, techStackPath,
|
|
4725
4917
|
telemetry, toolRegistryConfig, trajectoryRuntime, uniqueStrings, usageExactCommands,
|
|
4726
4918
|
validTaskScopePattern, validateNewWorkPlan, verifierCommandInstructions, verifyProjectCapabilityState, workingTreeEvidenceDigest,
|
|
4727
|
-
repositoryFileManifest, resolveTaskScopePatterns,
|
|
4919
|
+
repositoryFileManifest, repositoryFileManifestDetails, resolveTaskScopePatterns,
|
|
4728
4920
|
workingTreeSnapshot, workingTreeSnapshotHasUnavailableEvidence, writeContextIndex, writeProfileDocumentWithLock, writeProfileFromAdapter, writeProjectOnboarding,
|
|
4729
4921
|
writeTask, writeTechStackSelection
|
|
4730
4922
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { acceptanceReceiptProvenanceSummary, acceptanceReceiptSummary } from "./acceptance-receipt.js";
|
|
2
|
+
import { isDurableContextEvidenceEntry } from "./context-evidence.js";
|
|
2
3
|
import { runtimeLifecycleMode } from "./task-lifecycle.js";
|
|
3
4
|
import { latestObservedVerificationEvidence, meaningfulVerificationCommands, verificationEvidenceProvesStableTree } from "./verification-intelligence.js";
|
|
4
5
|
import { isCurrentWorkingTreeDigest, WORKING_TREE_DIGEST_ALGORITHM } from "./working-tree-digest.js";
|
|
@@ -25,6 +26,7 @@ export function compactTaskDetails(task) {
|
|
|
25
26
|
sessionId: task.sessionId,
|
|
26
27
|
sessionName: task.sessionName,
|
|
27
28
|
changeMode: task.changeMode,
|
|
29
|
+
mutationPolicy: task.mutationPolicy ?? (task.changeMode === "read-only" ? "forbidden" : "required"),
|
|
28
30
|
attempt: task.attempt,
|
|
29
31
|
maxAttempts: task.maxAttempts,
|
|
30
32
|
previousAttempts: task.previousAttempts,
|
|
@@ -51,12 +53,24 @@ export function compactTaskDetails(task) {
|
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
export function mergeObservedTaskContext(task, entries, maxManifestFiles, redact = (value) => String(value ?? "")) {
|
|
54
|
-
const known = new
|
|
56
|
+
const known = new Map((task.contextManifest ?? []).map((item, index) => [item.path, index]));
|
|
57
|
+
let durableCount = (task.contextManifest ?? []).filter(isDurableContextEvidenceEntry).length;
|
|
55
58
|
const added = [];
|
|
56
59
|
for (const entry of entries ?? []) {
|
|
57
|
-
|
|
60
|
+
const existingIndex = known.get(entry.path);
|
|
61
|
+
if (existingIndex !== undefined) {
|
|
62
|
+
const existing = task.contextManifest[existingIndex];
|
|
63
|
+
if (isDurableContextEvidenceEntry(existing) || !isDurableContextEvidenceEntry(entry)) continue;
|
|
64
|
+
if (durableCount >= maxManifestFiles) continue;
|
|
65
|
+
task.contextManifest[existingIndex] = { path: entry.path, reason: redact(entry.reason) };
|
|
66
|
+
durableCount += 1;
|
|
67
|
+
added.push(entry.path);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (durableCount >= maxManifestFiles || !isDurableContextEvidenceEntry(entry)) continue;
|
|
58
71
|
task.contextManifest.push({ path: entry.path, reason: redact(entry.reason) });
|
|
59
|
-
|
|
72
|
+
durableCount += 1;
|
|
73
|
+
known.set(entry.path, task.contextManifest.length - 1);
|
|
60
74
|
added.push(entry.path);
|
|
61
75
|
}
|
|
62
76
|
return added;
|
|
@@ -31,16 +31,20 @@ const ASSISTED_READ_ONLY_PLAN = [
|
|
|
31
31
|
{ id: "review", role: "piagent-reviewer", mode: "review", dependsOn: ["scout"] }
|
|
32
32
|
];
|
|
33
33
|
|
|
34
|
+
function mutationForbidden(task) {
|
|
35
|
+
return task?.changeMode === "read-only" || task?.mutationPolicy === "forbidden";
|
|
36
|
+
}
|
|
37
|
+
|
|
34
38
|
export function runtimeLifecycleMode(task) {
|
|
35
39
|
if (!Array.isArray(task?.workPlan)) return "manual";
|
|
36
40
|
if (
|
|
37
|
-
task
|
|
41
|
+
mutationForbidden(task)
|
|
38
42
|
&& (task.riskLane === "tiny" || (task.intakeMode === "runtime" && task.riskLane === "normal"))
|
|
39
43
|
&& task.workPlan.length === AUTOMATIC_READ_ONLY_PLAN.length
|
|
40
44
|
&& task.workPlan.every((step, index) => matchesStep(step, AUTOMATIC_READ_ONLY_PLAN[index]))
|
|
41
45
|
) return "automatic-readonly";
|
|
42
46
|
if (
|
|
43
|
-
task
|
|
47
|
+
mutationForbidden(task)
|
|
44
48
|
&& task.riskLane === "normal"
|
|
45
49
|
&& task.workPlan.length === ASSISTED_READ_ONLY_PLAN.length
|
|
46
50
|
&& task.workPlan.every((step, index) => matchesStep(step, ASSISTED_READ_ONLY_PLAN[index]))
|
|
@@ -68,6 +72,7 @@ function nonEmptyStrings(value) {
|
|
|
68
72
|
function sourceContractReady(task) {
|
|
69
73
|
return task?.schemaVersion === 2
|
|
70
74
|
&& task.changeMode === "source-change"
|
|
75
|
+
&& task.mutationPolicy !== "forbidden"
|
|
71
76
|
&& task.trace?.outcome === "pending"
|
|
72
77
|
&& nonEmptyStrings(task.acceptanceCriteria)
|
|
73
78
|
&& nonEmptyStrings(task.scope)
|