agentera 3.0.0-dev.39 → 3.0.0-dev.41
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/README.md +1 -1
- package/bundle/CHANGELOG.md +55 -8
- package/bundle/README.md +3 -3
- package/bundle/references/artifacts/glossary-entry-contract.yaml +18 -5
- package/bundle/references/artifacts/state-storage-authority.yaml +865 -171
- package/bundle/references/cli/agent-ready-state-contract.yaml +12 -12
- package/bundle/references/cli/audience-namespace-cli-migration.yaml +5 -5
- package/bundle/references/cli/prime-consumer-compatibility.yaml +58 -18
- package/bundle/references/cli/vocabulary-index.yaml +1 -1
- package/bundle/references/cli/vocabulary.md +4 -3
- package/bundle/skills/agentera/SKILL.md +23 -4
- package/bundle/skills/agentera/capabilities/build/schemas/artifacts.yaml +3 -3
- package/bundle/skills/agentera/capabilities/build/schemas/validation.yaml +3 -1
- package/bundle/skills/agentera/capabilities/discuss/schemas/artifacts.yaml +2 -1
- package/bundle/skills/agentera/capabilities/discuss/schemas/validation.yaml +3 -1
- package/bundle/skills/agentera/capabilities/plan/schemas/artifacts.yaml +2 -2
- package/bundle/skills/agentera/capabilities/plan/schemas/validation.yaml +3 -1
- package/bundle/skills/agentera/capabilities/profile/schemas/artifacts.yaml +3 -1
- package/bundle/skills/agentera/capabilities/status/schemas/artifacts.yaml +2 -1
- package/bundle/skills/agentera/capability_schema_contract.yaml +1 -1
- package/bundle/skills/agentera/schemas/artifacts/plan.yaml +89 -30
- package/bundle/skills/agentera/schemas/artifacts/todo.yaml +103 -20
- package/dist/analytics/extractCorpus/cli.js.map +1 -1
- package/dist/analytics/extractCorpus/copilotSessions.js.map +1 -1
- package/dist/analytics/extractCorpus/core.js.map +1 -1
- package/dist/analytics/extractCorpus/corpus.js.map +1 -1
- package/dist/analytics/extractCorpus/coverageAudit.js.map +1 -1
- package/dist/analytics/extractCorpus/cursorSessions.js.map +1 -1
- package/dist/analytics/extractCorpus/evidenceTiers.js.map +1 -1
- package/dist/analytics/extractCorpus/extractCorpusParity.js.map +1 -1
- package/dist/analytics/extractCorpus/filesystemSources.js.map +1 -1
- package/dist/analytics/extractCorpus/index.js.map +1 -1
- package/dist/analytics/extractCorpus/jsonlSessions.js.map +1 -1
- package/dist/analytics/extractCorpus/sqliteCaps.js.map +1 -1
- package/dist/analytics/extractCorpus/sqliteSessions.js.map +1 -1
- package/dist/analytics/extractCorpus/tierReader.js.map +1 -1
- package/dist/analytics/extractCorpus.js.map +1 -1
- package/dist/analytics/glossaryAdviceResolution.js.map +1 -1
- package/dist/analytics/glossaryInputAcquisition.js.map +1 -1
- package/dist/analytics/personalGlossaryAdmission.js.map +1 -1
- package/dist/analytics/personalGlossaryProfile.js +2 -1
- package/dist/analytics/personalGlossaryProfile.js.map +1 -1
- package/dist/analytics/profileSignals.js.map +1 -1
- package/dist/analytics/usageStats.js.map +1 -1
- package/dist/audit/terminologyDrift.js.map +1 -1
- package/dist/bin/agentera.js.map +1 -1
- package/dist/capabilities/audit/instructions.js.map +1 -1
- package/dist/capabilities/build/instructions.js +2 -2
- package/dist/capabilities/build/instructions.js.map +1 -1
- package/dist/capabilities/design/instructions.js.map +1 -1
- package/dist/capabilities/discuss/instructions.js +2 -1
- package/dist/capabilities/discuss/instructions.js.map +1 -1
- package/dist/capabilities/document/instructions.js.map +1 -1
- package/dist/capabilities/index.js +1 -1
- package/dist/capabilities/index.js.map +1 -1
- package/dist/capabilities/optimize/instructions.js +1 -0
- package/dist/capabilities/optimize/instructions.js.map +1 -1
- package/dist/capabilities/orchestrate/instructions.js.map +1 -1
- package/dist/capabilities/plan/instructions.js +3 -2
- package/dist/capabilities/plan/instructions.js.map +1 -1
- package/dist/capabilities/profile/instructions.js.map +1 -1
- package/dist/capabilities/research/instructions.js.map +1 -1
- package/dist/capabilities/status/instructions.js +8 -1
- package/dist/capabilities/status/instructions.js.map +1 -1
- package/dist/capabilities/status/startupInstructions.js.map +1 -1
- package/dist/capabilities/vision/instructions.js.map +1 -1
- package/dist/cli/appContext.js.map +1 -1
- package/dist/cli/argvalidate.js.map +1 -1
- package/dist/cli/capabilityContext/benchmark.js +2 -1
- package/dist/cli/capabilityContext/benchmark.js.map +1 -1
- package/dist/cli/capabilityContext/bespoke.js.map +1 -1
- package/dist/cli/capabilityContext/build.js +6 -6
- package/dist/cli/capabilityContext/build.js.map +1 -1
- package/dist/cli/capabilityContext/closeout.js +11 -7
- package/dist/cli/capabilityContext/closeout.js.map +1 -1
- package/dist/cli/capabilityContext/contract.js +4 -4
- package/dist/cli/capabilityContext/contract.js.map +1 -1
- package/dist/cli/capabilityContext/evidence.js +4 -4
- package/dist/cli/capabilityContext/evidence.js.map +1 -1
- package/dist/cli/capabilityContext/index.js.map +1 -1
- package/dist/cli/capabilityContext/orchestration.js +12 -7
- package/dist/cli/capabilityContext/orchestration.js.map +1 -1
- package/dist/cli/capabilityContext/planState.js.map +1 -1
- package/dist/cli/capabilityContext/progress.js.map +1 -1
- package/dist/cli/capabilityContext/projectVerification.js.map +1 -1
- package/dist/cli/capabilityContext/shared.js +2 -2
- package/dist/cli/capabilityContext/shared.js.map +1 -1
- package/dist/cli/capabilityContext/slim.js.map +1 -1
- package/dist/cli/capabilityContext/startup.js +6 -2
- package/dist/cli/capabilityContext/startup.js.map +1 -1
- package/dist/cli/capabilityContext/types.js +19 -14
- package/dist/cli/capabilityContext/types.js.map +1 -1
- package/dist/cli/capabilityContext.js.map +1 -1
- package/dist/cli/commands/appHome.js.map +1 -1
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/cli/commands/compact.js.map +1 -1
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/durability.js.map +1 -1
- package/dist/cli/commands/entityMigrate.js.map +1 -1
- package/dist/cli/commands/glossaryAdvice.js.map +1 -1
- package/dist/cli/commands/lint.js.map +1 -1
- package/dist/cli/commands/personalGlossary.js.map +1 -1
- package/dist/cli/commands/prime/briefOrientation.js +260 -44
- package/dist/cli/commands/prime/briefOrientation.js.map +1 -1
- package/dist/cli/commands/prime/buildExecutionRequest.js.map +1 -1
- package/dist/cli/commands/prime/bundleStatus.js.map +1 -1
- package/dist/cli/commands/prime/collectEntityOrientation.js +22 -13
- package/dist/cli/commands/prime/collectEntityOrientation.js.map +1 -1
- package/dist/cli/commands/prime/collectOrientationState.js +20 -28
- package/dist/cli/commands/prime/collectOrientationState.js.map +1 -1
- package/dist/cli/commands/prime/orientationOutput.js +4 -5
- package/dist/cli/commands/prime/orientationOutput.js.map +1 -1
- package/dist/cli/commands/prime/types.js.map +1 -1
- package/dist/cli/commands/prime/v1Migration.js.map +1 -1
- package/dist/cli/commands/prime.js +1 -1
- package/dist/cli/commands/prime.js.map +1 -1
- package/dist/cli/commands/profileGrounding.js +29 -50
- package/dist/cli/commands/profileGrounding.js.map +1 -1
- package/dist/cli/commands/query.js.map +1 -1
- package/dist/cli/commands/report.js.map +1 -1
- package/dist/cli/commands/route.js.map +1 -1
- package/dist/cli/commands/schema.js +4 -43
- package/dist/cli/commands/schema.js.map +1 -1
- package/dist/cli/commands/state/decisions.js +1 -1
- package/dist/cli/commands/state/decisions.js.map +1 -1
- package/dist/cli/commands/state/docs.js.map +1 -1
- package/dist/cli/commands/state/experimentRecords.js +28 -15
- package/dist/cli/commands/state/experimentRecords.js.map +1 -1
- package/dist/cli/commands/state/experiments.js.map +1 -1
- package/dist/cli/commands/state/get.js +6 -3
- package/dist/cli/commands/state/get.js.map +1 -1
- package/dist/cli/commands/state/health.js.map +1 -1
- package/dist/cli/commands/state/index.js +3 -46
- package/dist/cli/commands/state/index.js.map +1 -1
- package/dist/cli/commands/state/list.js +41 -22
- package/dist/cli/commands/state/list.js.map +1 -1
- package/dist/cli/commands/state/objective.js.map +1 -1
- package/dist/cli/commands/state/plan.js.map +1 -1
- package/dist/cli/commands/state/planTasks.js +26 -13
- package/dist/cli/commands/state/planTasks.js.map +1 -1
- package/dist/cli/commands/state/plans.js +31 -19
- package/dist/cli/commands/state/plans.js.map +1 -1
- package/dist/cli/commands/state/progress.js.map +1 -1
- package/dist/cli/commands/state/shared.js.map +1 -1
- package/dist/cli/commands/state/todo.js +2 -1
- package/dist/cli/commands/state/todo.js.map +1 -1
- package/dist/cli/commands/state/write.js +52 -48
- package/dist/cli/commands/state/write.js.map +1 -1
- package/dist/cli/commands/upgrade.js.map +1 -1
- package/dist/cli/commands/upgradeVerify.js.map +1 -1
- package/dist/cli/commands/validate.js +44 -4
- package/dist/cli/commands/validate.js.map +1 -1
- package/dist/cli/commands/verify.js.map +1 -1
- package/dist/cli/contracts/bundleStatus.js.map +1 -1
- package/dist/cli/contracts/orientationState.js.map +1 -1
- package/dist/cli/dispatch/argvParser.js.map +1 -1
- package/dist/cli/dispatch/check.js.map +1 -1
- package/dist/cli/dispatch/commands.js.map +1 -1
- package/dist/cli/dispatch/index.js +12 -4
- package/dist/cli/dispatch/index.js.map +1 -1
- package/dist/cli/dispatch/lifecycle.js.map +1 -1
- package/dist/cli/dispatch/prime.js.map +1 -1
- package/dist/cli/dispatch/shared.js.map +1 -1
- package/dist/cli/dispatch/state.js +53 -86
- package/dist/cli/dispatch/state.js.map +1 -1
- package/dist/cli/dispatch.js.map +1 -1
- package/dist/cli/errors.js.map +1 -1
- package/dist/cli/help.js +134 -48
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/migrationRequired.js.map +1 -1
- package/dist/cli/orientation/attention.js +8 -5
- package/dist/cli/orientation/attention.js.map +1 -1
- package/dist/cli/orientation/corpusCoverage.js.map +1 -1
- package/dist/cli/orientation.js +61 -38
- package/dist/cli/orientation.js.map +1 -1
- package/dist/cli/planArtifacts.js.map +1 -1
- package/dist/cli/planEvidence.js.map +1 -1
- package/dist/cli/planLifecycleState.js +3 -1
- package/dist/cli/planLifecycleState.js.map +1 -1
- package/dist/cli/planTaskIndex.js.map +1 -1
- package/dist/cli/prime-blob.js +2 -1
- package/dist/cli/prime-blob.js.map +1 -1
- package/dist/cli/profileAcquisition.js +168 -0
- package/dist/cli/profileAcquisition.js.map +1 -0
- package/dist/cli/startupCompletenessContract.js +5 -2
- package/dist/cli/startupCompletenessContract.js.map +1 -1
- package/dist/cli/stateQuery.js.map +1 -1
- package/dist/cli/structured.js.map +1 -1
- package/dist/cli/todoMarkdown.js +25 -11
- package/dist/cli/todoMarkdown.js.map +1 -1
- package/dist/cli/todoReadinessSelection.js +52 -17
- package/dist/cli/todoReadinessSelection.js.map +1 -1
- package/dist/cli/todoSeverity.js.map +1 -1
- package/dist/core/atomicWriter.js.map +1 -1
- package/dist/core/difflib.js.map +1 -1
- package/dist/core/envPaths.js.map +1 -1
- package/dist/core/jsonValue.js.map +1 -1
- package/dist/core/paths.js.map +1 -1
- package/dist/core/pyjson.js.map +1 -1
- package/dist/core/shell.js +5 -0
- package/dist/core/shell.js.map +1 -0
- package/dist/core/skillIdentity.js.map +1 -1
- package/dist/core/sourceRoot.js.map +1 -1
- package/dist/core/text.js.map +1 -1
- package/dist/core/toml.js.map +1 -1
- package/dist/core/yaml.js.map +1 -1
- package/dist/eval/evalSkills.js.map +1 -1
- package/dist/eval/hybridRouteEvaluation.js.map +1 -1
- package/dist/eval/semanticEval.js.map +1 -1
- package/dist/eval/semanticFixtures.js.map +1 -1
- package/dist/hooks/common.js.map +1 -1
- package/dist/hooks/compaction/apply.js.map +1 -1
- package/dist/hooks/compaction/dryRun.js.map +1 -1
- package/dist/hooks/compaction/index.js.map +1 -1
- package/dist/hooks/compaction/parse.js.map +1 -1
- package/dist/hooks/compaction/retention.js.map +1 -1
- package/dist/hooks/compaction/status.js.map +1 -1
- package/dist/hooks/compaction/types.js.map +1 -1
- package/dist/hooks/cursorPreToolUse.js.map +1 -1
- package/dist/hooks/cursorSessionStart.js.map +1 -1
- package/dist/hooks/projectHookInput.js.map +1 -1
- package/dist/hooks/sessionStart.js.map +1 -1
- package/dist/hooks/sessionStop.js.map +1 -1
- package/dist/hooks/validateArtifact/agentFacing.js.map +1 -1
- package/dist/hooks/validateArtifact/index.js.map +1 -1
- package/dist/hooks/validateArtifact/markdown.js.map +1 -1
- package/dist/hooks/validateArtifact/runtime.js.map +1 -1
- package/dist/hooks/validateArtifact/schema.js +6 -0
- package/dist/hooks/validateArtifact/schema.js.map +1 -1
- package/dist/hooks/validateArtifact/traversal.js.map +1 -1
- package/dist/hooks/validateArtifact/violations.js.map +1 -1
- package/dist/migrate/v2HandoffManifest.js.map +1 -1
- package/dist/registries/artifactProtocolIds.js.map +1 -1
- package/dist/registries/artifactRegistry.js.map +1 -1
- package/dist/registries/artifactSchemaProjection.js +46 -0
- package/dist/registries/artifactSchemaProjection.js.map +1 -0
- package/dist/registries/capabilityContract.js.map +1 -1
- package/dist/registries/evaluatorHandoffContract.js.map +1 -1
- package/dist/registries/evidenceTierContract.js.map +1 -1
- package/dist/registries/glossaryAdviceContract.js.map +1 -1
- package/dist/registries/glossaryCaveatContract.js.map +1 -1
- package/dist/registries/glossaryConsumerContractValidation.js +1 -5
- package/dist/registries/glossaryConsumerContractValidation.js.map +1 -1
- package/dist/registries/glossaryEntryContract.js +10 -0
- package/dist/registries/glossaryEntryContract.js.map +1 -1
- package/dist/registries/glossaryTermIdentity.js.map +1 -1
- package/dist/registries/glossaryTermOccurrence.js.map +1 -1
- package/dist/registries/hybridRoute.js.map +1 -1
- package/dist/registries/hybridRouteReceipt.js.map +1 -1
- package/dist/registries/packageRegistry.js.map +1 -1
- package/dist/registries/todoReadinessContract.js +49 -6
- package/dist/registries/todoReadinessContract.js.map +1 -1
- package/dist/registries/triggerLoader.js.map +1 -1
- package/dist/registries/verbosityBudgetContract.js.map +1 -1
- package/dist/release/releaseMetadata.js.map +1 -1
- package/dist/runtime/lifecycleAdapterContract.js.map +1 -1
- package/dist/runtime/lifecycleAdapters.js.map +1 -1
- package/dist/runtime/lifecycleAuthority.js.map +1 -1
- package/dist/runtime/lifecycleOperationContract.js.map +1 -1
- package/dist/runtime/lifecycleOperations.js.map +1 -1
- package/dist/runtime/lifecycleOwnershipJournal.js.map +1 -1
- package/dist/runtime/lifecyclePublication.js.map +1 -1
- package/dist/runtime/lifecycleSnapshot.js.map +1 -1
- package/dist/runtime/nativeResourceCleanup.js.map +1 -1
- package/dist/setup/codex/cli.js.map +1 -1
- package/dist/setup/codex/configToml.js.map +1 -1
- package/dist/setup/codex/constants.js.map +1 -1
- package/dist/setup/codex/installRoot.js.map +1 -1
- package/dist/setup/codex/state.js.map +1 -1
- package/dist/setup/codex.js.map +1 -1
- package/dist/setup/opencode.js.map +1 -1
- package/dist/setup/opencodeConstants.js.map +1 -1
- package/dist/setup/sharedSkill.js.map +1 -1
- package/dist/setup/smokeChecks.js.map +1 -1
- package/dist/state/archiveDiscovery.js +8 -5
- package/dist/state/archiveDiscovery.js.map +1 -1
- package/dist/state/archivePublication.js.map +1 -1
- package/dist/state/archiveRecovery.js.map +1 -1
- package/dist/state/archiveReplay.js.map +1 -1
- package/dist/state/canonicalMigrationRecord.js.map +1 -1
- package/dist/state/changelog.js.map +1 -1
- package/dist/state/decisionEntities.js +59 -20
- package/dist/state/decisionEntities.js.map +1 -1
- package/dist/state/decisionLegacyValidation.js.map +1 -1
- package/dist/state/decisionMigrationProvenance.js.map +1 -1
- package/dist/state/decisionOverlay.js.map +1 -1
- package/dist/state/decisionRevision.js.map +1 -1
- package/dist/state/decisionRevisionMigration.js.map +1 -1
- package/dist/state/directRetrieval.js.map +1 -1
- package/dist/state/durability.js.map +1 -1
- package/dist/state/entityCutover.js.map +1 -1
- package/dist/state/entityCutoverGit.js.map +1 -1
- package/dist/state/entityGlossaryAuthority.js.map +1 -1
- package/dist/state/entityListProjection.js +188 -0
- package/dist/state/entityListProjection.js.map +1 -0
- package/dist/state/entityListRuntimeRegistry.js +50 -0
- package/dist/state/entityListRuntimeRegistry.js.map +1 -0
- package/dist/state/entityMigrationCausality.js.map +1 -1
- package/dist/state/entityMigrationIdentity.js.map +1 -1
- package/dist/state/entityMigrationPreview.js +32 -3
- package/dist/state/entityMigrationPreview.js.map +1 -1
- package/dist/state/entityPublicationContext.js +607 -765
- package/dist/state/entityPublicationContext.js.map +1 -1
- package/dist/state/entityRetrievalHelp.js +476 -0
- package/dist/state/entityRetrievalHelp.js.map +1 -0
- package/dist/state/entityStorage.js.map +1 -1
- package/dist/state/exactReplacementRecovery.js +19 -0
- package/dist/state/exactReplacementRecovery.js.map +1 -0
- package/dist/state/experimentArchive.js.map +1 -1
- package/dist/state/experimentIdentity.js.map +1 -1
- package/dist/state/healthEntities.js +15 -16
- package/dist/state/healthEntities.js.map +1 -1
- package/dist/state/healthEntityValidation.js.map +1 -1
- package/dist/state/healthRepair.js.map +1 -1
- package/dist/state/installRoot.js.map +1 -1
- package/dist/state/legacyEntityCutoverEvidence.js.map +1 -1
- package/dist/state/legacyIdentity.js.map +1 -1
- package/dist/state/legacySummaryRecord.js.map +1 -1
- package/dist/state/listAccounting.js.map +1 -1
- package/dist/state/listClassification.js.map +1 -1
- package/dist/state/listCursor.js.map +1 -1
- package/dist/state/listRetrieval.js.map +1 -1
- package/dist/state/migrationSourceBinding.js.map +1 -1
- package/dist/state/objectiveExperimentEntities.js +22 -22
- package/dist/state/objectiveExperimentEntities.js.map +1 -1
- package/dist/state/planEntities.js +75 -29
- package/dist/state/planEntities.js.map +1 -1
- package/dist/state/planIdentity.js +4 -1
- package/dist/state/planIdentity.js.map +1 -1
- package/dist/state/planRetrieval.js.map +1 -1
- package/dist/state/planTaskRetrieval.js +41 -31
- package/dist/state/planTaskRetrieval.js.map +1 -1
- package/dist/state/progressEntities.js +40 -25
- package/dist/state/progressEntities.js.map +1 -1
- package/dist/state/progressGlossaryCaveat.js.map +1 -1
- package/dist/state/progressPublicationOrder.js.map +1 -1
- package/dist/state/projectRoot.js.map +1 -1
- package/dist/state/projectionPolicy.js +12 -5
- package/dist/state/projectionPolicy.js.map +1 -1
- package/dist/state/retrievalAuthority.js +86 -272
- package/dist/state/retrievalAuthority.js.map +1 -1
- package/dist/state/safeProjectFile.js.map +1 -1
- package/dist/state/startupAnalysis/benchmark.js.map +1 -1
- package/dist/state/startupAnalysis/contract.js.map +1 -1
- package/dist/state/startupAnalysis/helpers.js.map +1 -1
- package/dist/state/startupAnalysis/index.js.map +1 -1
- package/dist/state/startupAnalysis/metrics.js.map +1 -1
- package/dist/state/startupAnalysis/records.js.map +1 -1
- package/dist/state/startupAnalysis/report.js.map +1 -1
- package/dist/state/startupAnalysis/threshold.js.map +1 -1
- package/dist/state/startupAnalysis.js.map +1 -1
- package/dist/state/startupProjection.js.map +1 -1
- package/dist/state/stateMode.js +11 -8
- package/dist/state/stateMode.js.map +1 -1
- package/dist/state/stateStorageAuthority.js.map +1 -1
- package/dist/state/summaryEntityRead.js.map +1 -1
- package/dist/state/summaryMigrationProvenance.js.map +1 -1
- package/dist/state/summarySourceRowAuthority.js.map +1 -1
- package/dist/state/todoDocsEntities.js +811 -91
- package/dist/state/todoDocsEntities.js.map +1 -1
- package/dist/state/todoDocsEntityValidation.js +127 -2
- package/dist/state/todoDocsEntityValidation.js.map +1 -1
- package/dist/state/todoReconciliationActivation.js +63 -0
- package/dist/state/todoReconciliationActivation.js.map +1 -0
- package/dist/state/todoReconciliationTransaction.js +554 -0
- package/dist/state/todoReconciliationTransaction.js.map +1 -0
- package/dist/state/write/assign.js.map +1 -1
- package/dist/state/write/candidateMutation.js +1 -12
- package/dist/state/write/candidateMutation.js.map +1 -1
- package/dist/state/write/errors.js.map +1 -1
- package/dist/state/write/experimentPublication.js.map +1 -1
- package/dist/state/write/explain.js +198 -177
- package/dist/state/write/explain.js.map +1 -1
- package/dist/state/write/fields.js.map +1 -1
- package/dist/state/write/glossaryPublication.js.map +1 -1
- package/dist/state/write/grammar.js +271 -0
- package/dist/state/write/grammar.js.map +1 -0
- package/dist/state/write/helpers.js.map +1 -1
- package/dist/state/write/index.js.map +1 -1
- package/dist/state/write/input.js +351 -5
- package/dist/state/write/input.js.map +1 -1
- package/dist/state/write/lock.js.map +1 -1
- package/dist/state/write/mutation.js.map +1 -1
- package/dist/state/write/operations.js +140 -343
- package/dist/state/write/operations.js.map +1 -1
- package/dist/state/write/planEvaluation.js +4 -0
- package/dist/state/write/planEvaluation.js.map +1 -1
- package/dist/state/write/planPublication.js +48 -3
- package/dist/state/write/planPublication.js.map +1 -1
- package/dist/state/write/runtimeOperations.js +65 -0
- package/dist/state/write/runtimeOperations.js.map +1 -0
- package/dist/state/write/serialize.js.map +1 -1
- package/dist/state/write/transaction.js +13 -7
- package/dist/state/write/transaction.js.map +1 -1
- package/dist/state/write/validate.js.map +1 -1
- package/dist/upgrade/appContentRefresh.js.map +1 -1
- package/dist/upgrade/appModel.js.map +1 -1
- package/dist/upgrade/atomicWriter.js.map +1 -1
- package/dist/upgrade/bundleEvidence.js.map +1 -1
- package/dist/upgrade/channels.js.map +1 -1
- package/dist/upgrade/coexistenceProbe.js.map +1 -1
- package/dist/upgrade/compatibility.js.map +1 -1
- package/dist/upgrade/doctor.js.map +1 -1
- package/dist/upgrade/doctorClassifier.js.map +1 -1
- package/dist/upgrade/installedHooksRetirement.js.map +1 -1
- package/dist/upgrade/legacyAgentCleanup.js.map +1 -1
- package/dist/upgrade/lifecycleUpgrade.js.map +1 -1
- package/dist/upgrade/migrateArtifactsV2ToV3.js.map +1 -1
- package/dist/upgrade/migrationPublication.js.map +1 -1
- package/dist/upgrade/nextMajorDoctor.js.map +1 -1
- package/dist/upgrade/npxPlatformStatus.js.map +1 -1
- package/dist/upgrade/projectIntegration.js.map +1 -1
- package/dist/upgrade/projectIntegrationDecision.js.map +1 -1
- package/dist/upgrade/runtimeMigration.js.map +1 -1
- package/dist/upgrade/upgradeCommands.js.map +1 -1
- package/dist/upgrade/upgradeLock.js.map +1 -1
- package/dist/upgrade/upgradeOrchestrator.js.map +1 -1
- package/dist/upgrade/v3CapabilitySurface.js.map +1 -1
- package/dist/upgrade/versionResolution.js.map +1 -1
- package/dist/validate/appHomeContract.js.map +1 -1
- package/dist/validate/capability.js.map +1 -1
- package/dist/validate/crossCapability.js.map +1 -1
- package/dist/validate/selfAudit.js.map +1 -1
- package/dist/validate/v1LegacyCruft.js.map +1 -1
- package/dist/validate/vocabularyAuthority.js.map +1 -1
- package/package.json +2 -2
- package/dist/state/experimentRetrieval.js +0 -447
- package/dist/state/experimentRetrieval.js.map +0 -1
|
@@ -1,22 +1,29 @@
|
|
|
1
|
-
import { createHash
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import YAML from "yaml";
|
|
5
4
|
import { resolveSourceRoot } from "../core/sourceRoot.js";
|
|
6
5
|
import { canonicalRecordJson } from "./archiveDiscovery.js";
|
|
7
6
|
import { StateRetrievalFailure } from "./directRetrieval.js";
|
|
8
|
-
import { allocateEntityId, assertEntityDiscoveryOrigin, canonicalEntityRecordViolations, discoverEntities, entityExactGetMaxBytes, exactDiscoveredEntityBytes, publishEntityUnderLock, replaceEntityUnderLock, validateEntityState, withEntityWriterLock, } from "./entityStorage.js";
|
|
7
|
+
import { allocateEntityId, assertEntityDiscoveryOrigin, canonicalEntityEnvelopeBytes, canonicalEntityRecordViolations, discoverEntities, entityExactGetMaxBytes, exactDiscoveredEntityBytes, publishEntityUnderLock, replaceEntityUnderLock, validateEntityState, withEntityWriterLock, } from "./entityStorage.js";
|
|
9
8
|
import { detectStateModeBinding } from "./stateMode.js";
|
|
10
|
-
import { reject } from "./write/errors.js";
|
|
11
|
-
import { TODO_SEVERITIES, TODO_STATUSES, todoDocsRecordViolations } from "./todoDocsEntityValidation.js";
|
|
9
|
+
import { reject, StateWriteInputError } from "./write/errors.js";
|
|
10
|
+
import { TODO_SEVERITIES, TODO_STATUSES, todoDocsRecordViolations, todoInputViolations } from "./todoDocsEntityValidation.js";
|
|
12
11
|
import { todoReadinessReferenceViolations } from "../registries/todoReadinessContract.js";
|
|
13
12
|
import { loadStateStorageAuthority } from "./stateStorageAuthority.js";
|
|
13
|
+
import { entityListSelectorFlags, entityListSelectorKey, projectEntityList, resolveEntityListSelector } from "./entityListProjection.js";
|
|
14
|
+
import { decodeListCursor, encodeListCursor } from "./listCursor.js";
|
|
15
|
+
import { entityListFamily } from "./entityRetrievalHelp.js";
|
|
16
|
+
import { shellQuoteArgument } from "../core/shell.js";
|
|
17
|
+
import { parseTodoMarkdownListItem, renderTodoPublicRecord } from "../cli/todoMarkdown.js";
|
|
18
|
+
import { evaluateTodoReadinessQueue } from "../cli/todoReadinessSelection.js";
|
|
19
|
+
import { artifactSchemasDir, loadArtifactRecord, registryModelPath, resolveArtifactPath } from "../registries/artifactRegistry.js";
|
|
20
|
+
import { inspectTodoReconciliation, publishTodoReconciliation, recoverTodoReconciliation, todoCreateRequestSha256 } from "./todoReconciliationTransaction.js";
|
|
21
|
+
import { loadTodoReconciliationActivation, todoLegacyRowFingerprint, todoReconciliationActivationBytes, TODO_RECONCILIATION_ACTIVATION_PATH, TODO_RECONCILIATION_ITEM_LIMIT, } from "./todoReconciliationActivation.js";
|
|
14
22
|
const ID = /^[a-z]{10}$/;
|
|
15
|
-
const TODO = { artifact: "todo", boundary: "todo_item", order: "
|
|
23
|
+
const TODO = { artifact: "todo", boundary: "todo_item", order: "severity_then_status_then_markdown_order_then_id" };
|
|
16
24
|
const DOCS = { artifact: "docs", boundary: "documentation_inventory_entry", order: "path_then_id" };
|
|
17
25
|
function mapping(value) { return value !== null && typeof value === "object" && !Array.isArray(value); }
|
|
18
26
|
function relative(root, file) { return path.relative(path.resolve(root), file).split(path.sep).join("/"); }
|
|
19
|
-
function shell(value) { return `'${String(value).replaceAll("'", "'\\''")}'`; }
|
|
20
27
|
function definition(artifact) { return artifact === "todo" ? TODO : DOCS; }
|
|
21
28
|
function contract(boundary, sourceRoot = resolveSourceRoot()) {
|
|
22
29
|
const { authorityPath, document: authority } = loadStateStorageAuthority(sourceRoot);
|
|
@@ -31,7 +38,8 @@ function contract(boundary, sourceRoot = resolveSourceRoot()) {
|
|
|
31
38
|
return { authorityPath, entityRoot: storage.canonical_root, defaultLimit: positive(retrieval.default_limit, "default_limit"), maximumLimit: positive(retrieval.maximum_limit, "maximum_limit"), maxUtf8Bytes: positive(retrieval.max_utf8_bytes, "max_utf8_bytes") };
|
|
32
39
|
}
|
|
33
40
|
function failure(kind, artifact, message, recovery, id, exitCode = 1) {
|
|
34
|
-
|
|
41
|
+
const family = kind === "cursor_invalid" || kind === "cursor_snapshot_unavailable" ? entityListFamily(artifact) : undefined;
|
|
42
|
+
return new StateRetrievalFailure({ schemaVersion: "agentera.stateFailure.v1", status: "fail", error: { class: kind, message, syntax: family?.syntax ?? `agentera state ${artifact} get --id ID --format json`, example: family?.example ?? `agentera state ${artifact} get --id ${id ?? "qjtrmnpvka"} --format json`, recovery, artifact, ...(id ? { id } : {}) } }, exitCode);
|
|
35
43
|
}
|
|
36
44
|
function relevant(root, sourceRoot, artifact, supplied, sourceBinding) {
|
|
37
45
|
if (supplied)
|
|
@@ -62,6 +70,367 @@ function assertState(root, sourceRoot, sourceBinding) {
|
|
|
62
70
|
if (!state.valid)
|
|
63
71
|
reject({ class: "conflict", message: `canonical entity state is invalid: ${state.issues.map(({ message }) => message).join("; ")}`, recovery: "Run agentera check validate state and resolve every identity or record conflict before retrying; no state was changed." });
|
|
64
72
|
}
|
|
73
|
+
function markdownSeverity(section) {
|
|
74
|
+
return section === "critical" || section === "degraded" || section === "normal" || section === "annoying" ? section : null;
|
|
75
|
+
}
|
|
76
|
+
function todoPublicPath(root, sourceRoot) {
|
|
77
|
+
const todo = loadArtifactRecord("todo", artifactSchemasDir(sourceRoot), registryModelPath(sourceRoot));
|
|
78
|
+
if (!todo)
|
|
79
|
+
throw new Error("artifact registry is missing the canonical 'todo' record");
|
|
80
|
+
return resolveArtifactPath(todo, root, { strictWrite: true });
|
|
81
|
+
}
|
|
82
|
+
function todoReconciliationBinding(root, sourceRoot) {
|
|
83
|
+
const publicPath = relative(root, todoPublicPath(root, sourceRoot));
|
|
84
|
+
const docsRecord = loadArtifactRecord("docs", artifactSchemasDir(sourceRoot), registryModelPath(sourceRoot));
|
|
85
|
+
if (!docsRecord)
|
|
86
|
+
throw new Error("artifact registry is missing the canonical 'docs' record");
|
|
87
|
+
const docs = resolveArtifactPath(docsRecord, root, { strictWrite: true });
|
|
88
|
+
const mapping = fs.existsSync(docs) ? fs.readFileSync(docs) : Buffer.from("<absent>");
|
|
89
|
+
const mappingSha256 = createHash("sha256").update(publicPath).update("\0").update(mapping).digest("hex");
|
|
90
|
+
return { publicPath, mappingSha256 };
|
|
91
|
+
}
|
|
92
|
+
export function assertTodoReconciliationReadable(root, sourceRoot, id) {
|
|
93
|
+
const directory = path.join(root, ".agentera/.todo-reconciliation");
|
|
94
|
+
if (!fs.existsSync(directory) || !fs.readdirSync(directory).some((name) => name.endsWith(".json")))
|
|
95
|
+
return;
|
|
96
|
+
let pending;
|
|
97
|
+
try {
|
|
98
|
+
pending = inspectTodoReconciliation(root, todoReconciliationBinding(root, sourceRoot));
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (error instanceof StateWriteInputError) {
|
|
102
|
+
throw failure("unsupported_state", "todo", error.body.message, error.body.recovery ?? "Restore the pending TODO reconciliation journal, then retry this read.", id);
|
|
103
|
+
}
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
if (pending.length)
|
|
107
|
+
throw failure("unsupported_state", "todo", `TODO reconciliation transaction '${pending[0]}' is pending; no mixed TODO state is readable`, "Retry the exact non-dry-run TODO mutation to complete recovery, then repeat this read.", id);
|
|
108
|
+
}
|
|
109
|
+
const RECONCILIATION_VERSION = "agentera.todoReconciliation.v1";
|
|
110
|
+
const TODO_MARKDOWN_MAX_BYTES = 1024 * 1024;
|
|
111
|
+
const TODO_DRIFT_ITEM_LIMIT = 20;
|
|
112
|
+
const PUBLIC_FIELDS = ["description", "severity", "status"];
|
|
113
|
+
function publicSnapshot(record, order) {
|
|
114
|
+
return { present: true, description: renderTodoPublicRecord(record), severity: String(record.severity), status: String(record.status), ...(order === undefined ? {} : { order }) };
|
|
115
|
+
}
|
|
116
|
+
function baseline(record) {
|
|
117
|
+
const reconciliation = mapping(record.reconciliation) ? record.reconciliation : null;
|
|
118
|
+
const value = reconciliation?.schema_version === RECONCILIATION_VERSION && mapping(reconciliation.public) ? reconciliation.public : null;
|
|
119
|
+
if (!value || typeof value.present !== "boolean")
|
|
120
|
+
return null;
|
|
121
|
+
return structuredClone(value);
|
|
122
|
+
}
|
|
123
|
+
function samePublic(left, right, includeOrder = true) {
|
|
124
|
+
const fields = includeOrder ? ["present", "description", "severity", "status", "order"] : ["present", "description", "severity", "status"];
|
|
125
|
+
return fields.every((field) => left[field] === right[field]);
|
|
126
|
+
}
|
|
127
|
+
function changedPublicFields(current, prior, includeOrder) {
|
|
128
|
+
const fields = includeOrder ? ["present", ...PUBLIC_FIELDS, "order"] : ["present", ...PUBLIC_FIELDS];
|
|
129
|
+
return fields.filter((field) => current[field] !== prior[field]);
|
|
130
|
+
}
|
|
131
|
+
function todoAuthority() {
|
|
132
|
+
return {
|
|
133
|
+
identity: { owner: "managed_row", field: "id" },
|
|
134
|
+
public: { owner: "markdown", source: "TODO.md", fields: ["description", "severity", "status", "order"] },
|
|
135
|
+
operational: { owner: "agentera", source: "canonical_entity_file", fields: ["readiness", "dependencies", "blocked", "gate", "evidence", "lifecycle"] },
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function readTodoMarkdown(target) {
|
|
139
|
+
if (!fs.existsSync(target))
|
|
140
|
+
return { bytes: Buffer.from(""), text: "" };
|
|
141
|
+
const stat = fs.lstatSync(target);
|
|
142
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
143
|
+
reject({
|
|
144
|
+
class: "conflict",
|
|
145
|
+
message: "managed TODO Markdown must be a regular file",
|
|
146
|
+
recovery: "Restore the docs-mapped TODO artifact as a regular file within the project and retry; no state was changed.",
|
|
147
|
+
});
|
|
148
|
+
if (stat.size > TODO_MARKDOWN_MAX_BYTES)
|
|
149
|
+
reject({
|
|
150
|
+
class: "conflict",
|
|
151
|
+
message: `managed TODO Markdown exceeds the ${TODO_MARKDOWN_MAX_BYTES}-byte reconciliation bound`,
|
|
152
|
+
recovery: "Compact unmanaged or resolved Markdown content below the declared bound and retry; no state was changed.",
|
|
153
|
+
});
|
|
154
|
+
const bytes = fs.readFileSync(target);
|
|
155
|
+
let text;
|
|
156
|
+
try {
|
|
157
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
reject({ class: "conflict", message: "managed TODO Markdown is not valid UTF-8", recovery: "Restore valid UTF-8 TODO Markdown and retry; no state was changed." });
|
|
161
|
+
}
|
|
162
|
+
return { bytes, text: text };
|
|
163
|
+
}
|
|
164
|
+
function managedRows(markdown, activation, entities) {
|
|
165
|
+
const result = new Map();
|
|
166
|
+
const order = new Map();
|
|
167
|
+
const legacy = [];
|
|
168
|
+
let section = null;
|
|
169
|
+
markdown.split(/\r?\n/).forEach((line, index) => {
|
|
170
|
+
const heading = line.trim().match(/^##\s+(.+)$/)?.[1]?.toLowerCase();
|
|
171
|
+
if (heading)
|
|
172
|
+
section = heading.includes("critical") ? "critical" : heading.includes("degraded") ? "degraded" : heading.includes("annoying") ? "annoying" : heading.includes("resolved") ? "resolved" : heading.includes("normal") ? "normal" : null;
|
|
173
|
+
const item = parseTodoMarkdownListItem(line.trim());
|
|
174
|
+
if (!item)
|
|
175
|
+
return;
|
|
176
|
+
const severity = section ? markdownSeverity(section) : null;
|
|
177
|
+
if (item.id && !severity && section !== "resolved")
|
|
178
|
+
reject({ class: "conflict", message: `TODO '${item.id}' is outside a managed severity or resolved section`, recovery: `Move '${item.id}' under one declared TODO severity or the resolved section and retry; no state was changed.` });
|
|
179
|
+
if (!section)
|
|
180
|
+
return;
|
|
181
|
+
const key = section === "resolved" ? "resolved" : severity;
|
|
182
|
+
const publicOrder = (order.get(key) ?? 0) + 1;
|
|
183
|
+
order.set(key, publicOrder);
|
|
184
|
+
const row = { line: index, section: key, sourceLine: line, item, snapshot: { present: true, description: item.public_description ?? item.description, ...(section === "resolved" ? {} : { severity: severity }), status: item.status, order: publicOrder } };
|
|
185
|
+
if (!item.id) {
|
|
186
|
+
legacy.push(row);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (result.has(item.id))
|
|
190
|
+
reject({ class: "conflict", message: `TODO.md contains duplicate managed ID '${item.id}'`, recovery: `Keep exactly one managed row with ID '${item.id}' and retry; no state was changed.` });
|
|
191
|
+
result.set(item.id, { ...row, id: item.id });
|
|
192
|
+
});
|
|
193
|
+
const retainedLegacyRows = [];
|
|
194
|
+
if (activation) {
|
|
195
|
+
const retained = new Set(activation.retained_legacy_rows);
|
|
196
|
+
for (const row of legacy) {
|
|
197
|
+
const fingerprint = todoLegacyRowFingerprint(row.section, row.sourceLine);
|
|
198
|
+
if (retained.delete(fingerprint))
|
|
199
|
+
continue;
|
|
200
|
+
reject({
|
|
201
|
+
class: "conflict",
|
|
202
|
+
message: `managed TODO checkbox row at line ${row.line + 1} has no ten-letter ID after reconciliation activation`,
|
|
203
|
+
recovery: `Add '[id:abcdefghij]' with the row's canonical ten-letter entity ID at TODO.md line ${row.line + 1}, or move the row outside managed severity and resolved sections, then retry once; no state was changed.`,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
const legacyByFingerprint = new Map();
|
|
209
|
+
for (const row of legacy) {
|
|
210
|
+
const fingerprint = todoLegacyRowFingerprint(row.section, row.sourceLine);
|
|
211
|
+
const duplicates = legacyByFingerprint.get(fingerprint) ?? [];
|
|
212
|
+
duplicates.push(row);
|
|
213
|
+
legacyByFingerprint.set(fingerprint, duplicates);
|
|
214
|
+
}
|
|
215
|
+
const ambiguous = [...legacyByFingerprint.values()].find((rows) => rows.length > 1);
|
|
216
|
+
if (ambiguous)
|
|
217
|
+
reject({
|
|
218
|
+
class: "conflict",
|
|
219
|
+
message: `pre-activation TODO contains identical ID-less managed rows at lines ${ambiguous.map((row) => row.line + 1).join(", ")}`,
|
|
220
|
+
recovery: "Give each identical row its distinct canonical '[id:abcdefghij]' tag or remove the duplicate, then retry once; no state was changed.",
|
|
221
|
+
});
|
|
222
|
+
const claimed = new Set(result.keys());
|
|
223
|
+
for (const row of legacy) {
|
|
224
|
+
const matches = entities.filter((entity) => {
|
|
225
|
+
if (entity.boundary !== TODO.boundary || !entity.id || !entity.record || claimed.has(entity.id))
|
|
226
|
+
return false;
|
|
227
|
+
return samePublic(publicSnapshot(entity.record), rowSnapshot({ ...row, id: entity.id }, entity.record), false);
|
|
228
|
+
});
|
|
229
|
+
if (matches.length > 1)
|
|
230
|
+
reject({
|
|
231
|
+
class: "conflict",
|
|
232
|
+
message: `pre-activation TODO row at line ${row.line + 1} matches multiple canonical entities`,
|
|
233
|
+
recovery: `Add one exact '[id:abcdefghij]' tag to TODO.md line ${row.line + 1} and retry once; no state was changed.`,
|
|
234
|
+
});
|
|
235
|
+
const matched = matches[0];
|
|
236
|
+
if (matched?.id) {
|
|
237
|
+
claimed.add(matched.id);
|
|
238
|
+
result.set(matched.id, { ...row, id: matched.id });
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
const fingerprint = todoLegacyRowFingerprint(row.section, row.sourceLine);
|
|
242
|
+
if (retainedLegacyRows.includes(fingerprint))
|
|
243
|
+
reject({
|
|
244
|
+
class: "conflict",
|
|
245
|
+
message: `pre-activation TODO contains duplicate unmatched legacy rows at line ${row.line + 1}`,
|
|
246
|
+
recovery: `Give each duplicate row a distinct canonical '[id:abcdefghij]' tag or move it outside managed sections, then retry once; no state was changed.`,
|
|
247
|
+
});
|
|
248
|
+
retainedLegacyRows.push(fingerprint);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (result.size > TODO_RECONCILIATION_ITEM_LIMIT || retainedLegacyRows.length > TODO_RECONCILIATION_ITEM_LIMIT)
|
|
252
|
+
reject({
|
|
253
|
+
class: "conflict",
|
|
254
|
+
message: `TODO.md exceeds the ${TODO_RECONCILIATION_ITEM_LIMIT}-item reconciliation or legacy-activation bound`,
|
|
255
|
+
recovery: `Compact retained resolved rows until each bounded set has at most ${TODO_RECONCILIATION_ITEM_LIMIT} items, then retry once; no state was changed.`,
|
|
256
|
+
});
|
|
257
|
+
return { rows: result, retainedLegacyRows };
|
|
258
|
+
}
|
|
259
|
+
function rowSnapshot(row, record) {
|
|
260
|
+
return { ...row.snapshot, severity: row.section === "resolved" ? String(record.severity) : row.snapshot.severity };
|
|
261
|
+
}
|
|
262
|
+
function inspectTodoReadView(root, sourceRoot, entities) {
|
|
263
|
+
const activation = loadTodoReconciliationActivation(root)?.record ?? null;
|
|
264
|
+
if (!activation) {
|
|
265
|
+
return {
|
|
266
|
+
rows: new Map(),
|
|
267
|
+
drift: {
|
|
268
|
+
schema_version: RECONCILIATION_VERSION,
|
|
269
|
+
status: "inactive",
|
|
270
|
+
read_effect: "none",
|
|
271
|
+
authority: todoAuthority(),
|
|
272
|
+
counts: { managed: 0, drifted: 0, conflicts: 0 },
|
|
273
|
+
items: [],
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
const markdownPath = todoPublicPath(root, sourceRoot);
|
|
278
|
+
const markdown = readTodoMarkdown(markdownPath).text;
|
|
279
|
+
const todoEntities = entities.filter(({ boundary }) => boundary === TODO.boundary);
|
|
280
|
+
const rows = managedRows(markdown, activation, todoEntities).rows;
|
|
281
|
+
const items = [];
|
|
282
|
+
const entityIds = new Set();
|
|
283
|
+
for (const entity of todoEntities) {
|
|
284
|
+
const id = entity.id;
|
|
285
|
+
entityIds.add(id);
|
|
286
|
+
const current = entity.record;
|
|
287
|
+
const prior = baseline(current);
|
|
288
|
+
const row = rows.get(id);
|
|
289
|
+
if (!prior) {
|
|
290
|
+
items.push({ id, state: "conflict", markdown_changed_fields: [], entity_changed_fields: [], conflicting_fields: ["baseline"] });
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (!row) {
|
|
294
|
+
if (prior.present) {
|
|
295
|
+
items.push({
|
|
296
|
+
id,
|
|
297
|
+
state: prior.status === "open" ? "conflict" : "markdown_only",
|
|
298
|
+
markdown_changed_fields: ["present"],
|
|
299
|
+
entity_changed_fields: [],
|
|
300
|
+
conflicting_fields: prior.status === "open" ? ["present"] : [],
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
const markdownPublic = rowSnapshot(row, current);
|
|
306
|
+
const entityPublic = publicSnapshot(current, prior.order);
|
|
307
|
+
const markdownFields = changedPublicFields(markdownPublic, prior, true);
|
|
308
|
+
const entityFields = changedPublicFields(entityPublic, prior, false);
|
|
309
|
+
if (!markdownFields.length && !entityFields.length)
|
|
310
|
+
continue;
|
|
311
|
+
const conflictingFields = markdownFields.filter((field) => entityFields.includes(field)
|
|
312
|
+
&& markdownPublic[field] !== entityPublic[field]);
|
|
313
|
+
items.push({
|
|
314
|
+
id,
|
|
315
|
+
state: conflictingFields.length ? "conflict" : markdownFields.length && entityFields.length ? "convergent" : markdownFields.length ? "markdown_only" : "entity_only",
|
|
316
|
+
markdown_changed_fields: markdownFields,
|
|
317
|
+
entity_changed_fields: entityFields,
|
|
318
|
+
conflicting_fields: conflictingFields,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
for (const id of rows.keys()) {
|
|
322
|
+
if (!entityIds.has(id))
|
|
323
|
+
items.push({ id, state: "conflict", markdown_changed_fields: ["identity"], entity_changed_fields: [], conflicting_fields: ["identity"] });
|
|
324
|
+
}
|
|
325
|
+
items.sort((left, right) => left.id.localeCompare(right.id));
|
|
326
|
+
const conflicts = items.filter(({ state }) => state === "conflict").length;
|
|
327
|
+
const visibleItems = items.slice(0, TODO_DRIFT_ITEM_LIMIT);
|
|
328
|
+
const omitted = items.length - visibleItems.length;
|
|
329
|
+
return {
|
|
330
|
+
rows,
|
|
331
|
+
drift: {
|
|
332
|
+
schema_version: RECONCILIATION_VERSION,
|
|
333
|
+
status: conflicts ? "conflict" : items.length ? "drift" : "clean",
|
|
334
|
+
read_effect: "none",
|
|
335
|
+
next_write_boundary: "atomic_reconciliation",
|
|
336
|
+
authority: todoAuthority(),
|
|
337
|
+
counts: { managed: rows.size, drifted: items.length, conflicts },
|
|
338
|
+
items: visibleItems,
|
|
339
|
+
...(omitted ? { omitted: true, omitted_count: omitted, omission_reason: "item_limit" } : {}),
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
export function inspectTodoReconciliationDrift(root, sourceRoot = resolveSourceRoot(), discovery) {
|
|
344
|
+
assertTodoReconciliationReadable(root, sourceRoot);
|
|
345
|
+
return inspectTodoReadView(root, sourceRoot, relevant(root, sourceRoot, "todo", discovery)).drift;
|
|
346
|
+
}
|
|
347
|
+
export function projectTodoReadEntities(root, sourceRoot = resolveSourceRoot(), discovery) {
|
|
348
|
+
assertTodoReconciliationReadable(root, sourceRoot);
|
|
349
|
+
const entities = relevant(root, sourceRoot, "todo", discovery).filter(({ boundary }) => boundary === TODO.boundary);
|
|
350
|
+
if (entities.length > TODO_RECONCILIATION_ITEM_LIMIT)
|
|
351
|
+
throw failure("unsupported_state", "todo", `complete TODO projection exceeds the ${TODO_RECONCILIATION_ITEM_LIMIT}-entity startup bound`, "Compact resolved TODO entities within the declared reconciliation bound, then retry this read.");
|
|
352
|
+
const view = inspectTodoReadView(root, sourceRoot, entities);
|
|
353
|
+
const projected = view.drift.status !== "inactive";
|
|
354
|
+
return sorted("todo", entities, view.rows).map((entity) => {
|
|
355
|
+
const row = view.rows.get(entity.id);
|
|
356
|
+
return {
|
|
357
|
+
id: entity.id,
|
|
358
|
+
artifact: entity.artifact,
|
|
359
|
+
record: publicReadRecord(entity.record, row),
|
|
360
|
+
...(projected ? {
|
|
361
|
+
projectedOrder: row?.snapshot.order === undefined
|
|
362
|
+
? { kind: "absent" }
|
|
363
|
+
: { kind: "managed", markdownOrder: row.snapshot.order },
|
|
364
|
+
} : {}),
|
|
365
|
+
};
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
function publicReadRecord(record, row) {
|
|
369
|
+
return row ? importMarkdown(record, row) : record;
|
|
370
|
+
}
|
|
371
|
+
function publicReadMetadata(record, row) {
|
|
372
|
+
const value = row ? rowSnapshot(row, record) : { present: false };
|
|
373
|
+
return { ...value, owner: "markdown", source: "TODO.md" };
|
|
374
|
+
}
|
|
375
|
+
function importMarkdown(record, row) {
|
|
376
|
+
const result = structuredClone(record);
|
|
377
|
+
const description = row.item.public_description ?? row.item.description;
|
|
378
|
+
if (result.title !== undefined) {
|
|
379
|
+
result.kind = row.item.kind ?? result.kind;
|
|
380
|
+
result.target_version = row.item.target_version ?? null;
|
|
381
|
+
result.title = row.item.title ?? description;
|
|
382
|
+
}
|
|
383
|
+
else
|
|
384
|
+
result.description = description;
|
|
385
|
+
if (row.section !== "resolved")
|
|
386
|
+
result.severity = row.snapshot.severity;
|
|
387
|
+
result.status = row.item.status;
|
|
388
|
+
return result;
|
|
389
|
+
}
|
|
390
|
+
function withBaseline(record, value) {
|
|
391
|
+
const publicValue = Object.fromEntries(Object.entries(value).filter(([, field]) => field !== undefined));
|
|
392
|
+
return { ...record, reconciliation: { schema_version: RECONCILIATION_VERSION, public: publicValue } };
|
|
393
|
+
}
|
|
394
|
+
function sectionFor(record) { return record.status === "resolved" ? "resolved" : String(record.severity); }
|
|
395
|
+
function headingFor(section) { return section === "resolved" ? "## ✓ Resolved" : `## → ${section[0].toUpperCase()}${section.slice(1)}`; }
|
|
396
|
+
function rowFor(id, record) { return `- [${record.status === "resolved" ? "x" : " "}] [id:${id}] ${renderTodoPublicRecord(record)}`; }
|
|
397
|
+
function renderManagedMarkdown(markdown, records, existing) {
|
|
398
|
+
const byLine = new Map([...existing.values()].map((row) => [row.line, row]));
|
|
399
|
+
const retained = new Set();
|
|
400
|
+
const lines = markdown.split(/\r?\n/).flatMap((line, index) => {
|
|
401
|
+
const row = byLine.get(index);
|
|
402
|
+
if (!row)
|
|
403
|
+
return [line];
|
|
404
|
+
const record = records.get(row.id);
|
|
405
|
+
if (!record || sectionFor(record) !== row.section)
|
|
406
|
+
return [];
|
|
407
|
+
retained.add(row.id);
|
|
408
|
+
return [rowFor(row.id, record)];
|
|
409
|
+
});
|
|
410
|
+
for (const section of ["critical", "degraded", "normal", "annoying", "resolved"]) {
|
|
411
|
+
const ids = [...records.entries()].filter(([id, record]) => !retained.has(id) && sectionFor(record) === section).sort(([left], [right]) => {
|
|
412
|
+
const a = existing.get(left);
|
|
413
|
+
const b = existing.get(right);
|
|
414
|
+
return (a?.section === section ? a.snapshot.order : Number.MAX_SAFE_INTEGER) - (b?.section === section ? b.snapshot.order : Number.MAX_SAFE_INTEGER) || left.localeCompare(right);
|
|
415
|
+
}).map(([id]) => id);
|
|
416
|
+
if (!ids.length)
|
|
417
|
+
continue;
|
|
418
|
+
let heading = lines.findIndex((line) => line.trim().toLowerCase() === headingFor(section).toLowerCase());
|
|
419
|
+
if (heading < 0) {
|
|
420
|
+
if (lines.at(-1)?.trim())
|
|
421
|
+
lines.push("");
|
|
422
|
+
lines.push(headingFor(section));
|
|
423
|
+
heading = lines.length - 1;
|
|
424
|
+
}
|
|
425
|
+
let insert = heading + 1;
|
|
426
|
+
while (insert < lines.length && !/^##\s+/.test(lines[insert].trim()))
|
|
427
|
+
insert += 1;
|
|
428
|
+
while (insert > heading + 1 && !lines[insert - 1].trim())
|
|
429
|
+
insert -= 1;
|
|
430
|
+
lines.splice(insert, 0, ...ids.map((id) => rowFor(id, records.get(id))));
|
|
431
|
+
}
|
|
432
|
+
return `${lines.join("\n").replace(/\n*$/, "")}\n`;
|
|
433
|
+
}
|
|
65
434
|
function envelope(command, entity, artifact, record, dryRun) {
|
|
66
435
|
return { schemaVersion: "agentera.stateWrite.v1", command, status: "pass", path: entity.path, id: entity.id, artifact, record, operation: { verb: command.split(" ").at(-1), dry_run: dryRun, idempotent_replay: entity.replay }, validation: { status: "pass", violations: [] } };
|
|
67
436
|
}
|
|
@@ -69,31 +438,145 @@ function targetPath(root, sourceRoot, artifact, id) {
|
|
|
69
438
|
const model = definition(artifact);
|
|
70
439
|
return path.join(root, contract(model.boundary, sourceRoot).entityRoot, artifact, model.boundary, `${id}.yaml`);
|
|
71
440
|
}
|
|
72
|
-
function
|
|
73
|
-
if (
|
|
74
|
-
return structuredClone(
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
441
|
+
function readinessRecord(value) {
|
|
442
|
+
if (!mapping(value))
|
|
443
|
+
return structuredClone(value);
|
|
444
|
+
const dependencies = Array.isArray(value.dependencies)
|
|
445
|
+
? value.dependencies.map((dependency) => typeof dependency === "string" ? { artifact: "todo", id: dependency } : dependency)
|
|
446
|
+
: value.dependencies;
|
|
447
|
+
return {
|
|
448
|
+
capability: value.capability,
|
|
449
|
+
reason: value.reason,
|
|
450
|
+
dependencies: dependencies ?? [],
|
|
451
|
+
blocked: value.blocked ?? null,
|
|
452
|
+
gate: value.gate ?? null,
|
|
453
|
+
queue_rank: value.queue_rank,
|
|
454
|
+
order_reason: value.order_reason,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
function legacyTodoPayload(req) {
|
|
458
|
+
const values = req.values;
|
|
459
|
+
return {
|
|
460
|
+
...(values.severity !== undefined ? { severity: values.severity } : {}),
|
|
461
|
+
...(values.description !== undefined ? { description: values.description } : {}),
|
|
462
|
+
...(values.readiness !== undefined ? { readiness: readinessRecord(values.readiness) } : {}),
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
function todoPayload(req) {
|
|
466
|
+
return req.input ? structuredClone(req.input) : legacyTodoPayload(req);
|
|
467
|
+
}
|
|
468
|
+
function applyTodoPatch(current, patch) {
|
|
469
|
+
const record = structuredClone(current);
|
|
470
|
+
if (patch.title !== undefined && record.title === undefined && typeof record.description === "string") {
|
|
471
|
+
const legacy = parseTodoMarkdownListItem(`- [ ] ${record.description}`);
|
|
472
|
+
record.kind = legacy?.kind ?? "task";
|
|
473
|
+
record.target_version = legacy?.target_version ?? null;
|
|
474
|
+
record.requirements = [];
|
|
475
|
+
record.acceptance = [];
|
|
476
|
+
record.release_blocker = false;
|
|
477
|
+
}
|
|
478
|
+
for (const field of ["kind", "target_version", "title", "requirements", "acceptance", "release_blocker", "severity", "description"]) {
|
|
479
|
+
if (!(field in patch))
|
|
480
|
+
continue;
|
|
481
|
+
const value = patch[field];
|
|
482
|
+
if (value === null)
|
|
483
|
+
delete record[field];
|
|
484
|
+
else
|
|
485
|
+
record[field] = structuredClone(value);
|
|
486
|
+
}
|
|
487
|
+
if ("readiness" in patch) {
|
|
488
|
+
if (patch.readiness === null)
|
|
489
|
+
delete record.readiness;
|
|
490
|
+
else
|
|
491
|
+
record.readiness = readinessRecord(patch.readiness);
|
|
492
|
+
}
|
|
493
|
+
if ("title" in patch)
|
|
494
|
+
delete record.description;
|
|
495
|
+
return record;
|
|
496
|
+
}
|
|
497
|
+
function transitionRecord(req, current, entities) {
|
|
498
|
+
const lifecycle = mapping(req.values.lifecycle) ? req.values.lifecycle : {};
|
|
499
|
+
const operation = req.spec.verb;
|
|
500
|
+
const reason = String(lifecycle.reason ?? "").trim();
|
|
501
|
+
const date = String(lifecycle.date ?? "");
|
|
502
|
+
const severity = String(req.values.severity ?? "");
|
|
503
|
+
const replacement = String(lifecycle.replacement ?? "");
|
|
504
|
+
const record = structuredClone(current);
|
|
505
|
+
const previous = mapping(current.lifecycle) ? current.lifecycle : null;
|
|
506
|
+
const sameTransition = previous !== null
|
|
507
|
+
&& previous.operation === operation
|
|
508
|
+
&& previous.reason === reason
|
|
509
|
+
&& previous.date === date
|
|
510
|
+
&& (operation === "set-severity" ? current.severity === severity : true)
|
|
511
|
+
&& (operation === "supersede" ? previous.replacement === replacement : previous.replacement === undefined);
|
|
512
|
+
const replayStatus = operation === "resolve" || operation === "supersede" ? "resolved" : operation === "reopen" ? "open" : null;
|
|
513
|
+
if (sameTransition && (replayStatus === null || current.status === replayStatus))
|
|
514
|
+
return record;
|
|
515
|
+
if (operation === "set-severity") {
|
|
516
|
+
if (previous?.operation === operation && previous.reason === reason && previous.date === date)
|
|
517
|
+
reject({
|
|
518
|
+
class: "conflict",
|
|
519
|
+
message: "TODO set-severity retry differs from the established transition",
|
|
520
|
+
recovery: `Retry the exact transition with --severity ${String(current.severity)}, --reason ${shellQuoteArgument(previous.reason)}, and --date ${String(previous.date)}; use a distinct reason or date for a new transition; no state was changed.`,
|
|
521
|
+
});
|
|
522
|
+
record.severity = severity;
|
|
92
523
|
}
|
|
93
|
-
if (
|
|
524
|
+
else if (operation === "resolve") {
|
|
525
|
+
if (current.status !== "open")
|
|
526
|
+
reject({
|
|
527
|
+
class: "conflict",
|
|
528
|
+
message: "TODO resolve requires an open item",
|
|
529
|
+
recovery: previous?.operation === operation
|
|
530
|
+
? `Retry the exact transition with --reason ${shellQuoteArgument(previous.reason)} and --date ${String(previous.date)}; no state was changed.`
|
|
531
|
+
: "Use state todo reopen only for a resolved item; no state was changed.",
|
|
532
|
+
});
|
|
94
533
|
record.status = "resolved";
|
|
534
|
+
}
|
|
535
|
+
else if (operation === "reopen") {
|
|
536
|
+
if (current.status !== "resolved")
|
|
537
|
+
reject({
|
|
538
|
+
class: "conflict",
|
|
539
|
+
message: "TODO reopen requires a resolved item",
|
|
540
|
+
recovery: previous?.operation === operation
|
|
541
|
+
? `Retry the exact transition with --reason ${shellQuoteArgument(previous.reason)} and --date ${String(previous.date)}; no state was changed.`
|
|
542
|
+
: "Use state todo resolve for an open item; no state was changed.",
|
|
543
|
+
});
|
|
544
|
+
record.status = "open";
|
|
545
|
+
}
|
|
546
|
+
else if (operation === "supersede") {
|
|
547
|
+
if (current.status !== "open")
|
|
548
|
+
reject({
|
|
549
|
+
class: "conflict",
|
|
550
|
+
message: "TODO supersede requires an open item",
|
|
551
|
+
recovery: previous?.operation === operation
|
|
552
|
+
? `Retry the exact transition with --replacement ${String(previous.replacement)}, --reason ${shellQuoteArgument(previous.reason)}, and --date ${String(previous.date)}; no state was changed.`
|
|
553
|
+
: "Reopen the resolved item before establishing a supersession; no state was changed.",
|
|
554
|
+
});
|
|
555
|
+
if (!/^[a-z]{10}$/.test(replacement) || replacement === String(req.values.id))
|
|
556
|
+
reject({ class: "schema_violation", message: "supersede replacement must be a distinct bare TODO ID", recovery: "Use an existing ten-letter TODO ID other than the selected item; no state was changed." });
|
|
557
|
+
const target = entities.find((entity) => entity.boundary === TODO.boundary && entity.id === replacement);
|
|
558
|
+
if (!target)
|
|
559
|
+
reject({ class: "unsupported_target", message: `TODO replacement '${replacement}' was not found`, recovery: "Use an ID returned by agentera state todo list --format json; no state was changed." });
|
|
560
|
+
record.status = "resolved";
|
|
561
|
+
}
|
|
562
|
+
record.lifecycle = {
|
|
563
|
+
operation,
|
|
564
|
+
reason,
|
|
565
|
+
date,
|
|
566
|
+
...(lifecycle.replacement !== undefined ? { replacement: String(lifecycle.replacement) } : {}),
|
|
567
|
+
};
|
|
95
568
|
return record;
|
|
96
569
|
}
|
|
570
|
+
function mutationRecord(req, current, entities = []) {
|
|
571
|
+
if (req.artifact === "docs")
|
|
572
|
+
return structuredClone(req.input ?? {});
|
|
573
|
+
if (current && ["set-severity", "supersede", "resolve", "reopen"].includes(req.spec.verb))
|
|
574
|
+
return transitionRecord(req, current, entities);
|
|
575
|
+
const payload = todoPayload(req);
|
|
576
|
+
if (req.spec.verb === "create")
|
|
577
|
+
return { ...structuredClone(payload), status: "open", ...(payload.readiness !== undefined ? { readiness: readinessRecord(payload.readiness) } : {}) };
|
|
578
|
+
return applyTodoPatch(current ?? {}, payload);
|
|
579
|
+
}
|
|
97
580
|
function assertTodoReferences(id, record, entities) {
|
|
98
581
|
if (record.readiness === undefined)
|
|
99
582
|
return;
|
|
@@ -111,7 +594,75 @@ function assertTodoReferences(id, record, entities) {
|
|
|
111
594
|
});
|
|
112
595
|
}
|
|
113
596
|
function todoReadinessRecovery(verb) {
|
|
114
|
-
return `Run agentera state todo explain --verb ${verb} --format json, then
|
|
597
|
+
return `Run agentera state todo explain --verb ${verb} --format json, then provide a complete typed TODO record or patch; readiness must include capability, reason, queue_rank, and order_reason together, and dependencies must use IDs returned by agentera state todo list --format json.`;
|
|
598
|
+
}
|
|
599
|
+
function reconcileTodoRecords(entities, rows, activating) {
|
|
600
|
+
const records = new Map();
|
|
601
|
+
const visible = new Set();
|
|
602
|
+
for (const entity of entities.filter(({ boundary }) => boundary === TODO.boundary)) {
|
|
603
|
+
const id = entity.id;
|
|
604
|
+
const current = entity.record;
|
|
605
|
+
const row = rows.get(id);
|
|
606
|
+
const prior = baseline(current);
|
|
607
|
+
if (!prior) {
|
|
608
|
+
if (activating) {
|
|
609
|
+
records.set(id, row ? importMarkdown(current, row) : current);
|
|
610
|
+
if (row || current.status === "open")
|
|
611
|
+
visible.add(id);
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
reject({
|
|
615
|
+
class: "conflict",
|
|
616
|
+
message: `TODO '${id}' has no stable reconciliation baseline`,
|
|
617
|
+
recovery: `Restore TODO '${id}' with its last committed reconciliation baseline, or restore the pre-activation state and remove the activation marker, then retry once; no state was changed.`,
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
if (!row) {
|
|
621
|
+
if (prior.present && prior.status === "open")
|
|
622
|
+
reject({
|
|
623
|
+
class: "conflict",
|
|
624
|
+
message: `unchecked TODO '${id}' was removed from TODO.md`,
|
|
625
|
+
recovery: `Restore the unchecked managed row '${id}' or check it resolved before removing it, then retry once; no state was changed.`,
|
|
626
|
+
});
|
|
627
|
+
records.set(id, current);
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
const markdown = rowSnapshot(row, current);
|
|
631
|
+
const entityPublic = publicSnapshot(current, prior.order);
|
|
632
|
+
const markdownFields = changedPublicFields(markdown, prior, true);
|
|
633
|
+
const entityFields = changedPublicFields(entityPublic, prior, false);
|
|
634
|
+
const conflictingFields = markdownFields.filter((field) => entityFields.includes(field)
|
|
635
|
+
&& markdown[field] !== entityPublic[field]);
|
|
636
|
+
if (conflictingFields.length)
|
|
637
|
+
reject({
|
|
638
|
+
class: "conflict",
|
|
639
|
+
message: `TODO.md and Agentera changed public fields divergently for TODO '${id}': ${conflictingFields.join(", ")}`,
|
|
640
|
+
recovery: `Choose one value for each divergent public field of '${id}', make both sides agree, then retry once; no state was changed.`,
|
|
641
|
+
});
|
|
642
|
+
let merged = current;
|
|
643
|
+
if (markdownFields.some((field) => PUBLIC_FIELDS.includes(field))) {
|
|
644
|
+
const imported = importMarkdown(current, row);
|
|
645
|
+
merged = structuredClone(current);
|
|
646
|
+
if (markdownFields.includes("description")) {
|
|
647
|
+
for (const field of ["description", "kind", "target_version", "title"]) {
|
|
648
|
+
if (imported[field] === undefined)
|
|
649
|
+
delete merged[field];
|
|
650
|
+
else
|
|
651
|
+
merged[field] = structuredClone(imported[field]);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
if (markdownFields.includes("severity"))
|
|
655
|
+
merged.severity = imported.severity;
|
|
656
|
+
if (markdownFields.includes("status"))
|
|
657
|
+
merged.status = imported.status;
|
|
658
|
+
}
|
|
659
|
+
records.set(id, merged);
|
|
660
|
+
visible.add(id);
|
|
661
|
+
}
|
|
662
|
+
for (const id of rows.keys())
|
|
663
|
+
if (!records.has(id))
|
|
664
|
+
reject({ class: "conflict", message: `TODO.md managed ID '${id}' has no canonical entity`, recovery: `Restore the canonical TODO entity for '${id}' or remove the orphaned managed ID, then retry once; no state was changed.` });
|
|
665
|
+
return { records, visible };
|
|
115
666
|
}
|
|
116
667
|
export function mutateTodoDocsEntity(req, options = {}) {
|
|
117
668
|
const artifact = req.artifact;
|
|
@@ -134,31 +685,146 @@ export function mutateTodoDocsEntity(req, options = {}) {
|
|
|
134
685
|
const pinnedRoot = context.pinnedPath();
|
|
135
686
|
const sourceBinding = { kind: "project", projectRoot: context.validatedRoot };
|
|
136
687
|
context.assertValid();
|
|
688
|
+
const todoBinding = artifact === "todo" ? todoReconciliationBinding(req.projectRoot, sourceRoot) : null;
|
|
689
|
+
const pending = todoBinding ? inspectTodoReconciliation(pinnedRoot, todoBinding) : [];
|
|
690
|
+
if (req.dryRun && pending.length)
|
|
691
|
+
reject({ class: "conflict", message: `TODO reconciliation transaction '${pending[0]}' requires recovery before dry-run`, recovery: "Retry the exact TODO mutation without --dry-run once to complete recovery; this dry-run changed no state." });
|
|
692
|
+
const createRequest = artifact === "todo" && req.spec.verb === "create" ? mutationRecord(req, undefined) : null;
|
|
693
|
+
const createRequestSha256 = createRequest ? todoCreateRequestSha256(createRequest) : undefined;
|
|
694
|
+
const recoveryReceipts = todoBinding && !req.dryRun
|
|
695
|
+
? recoverTodoReconciliation(context, sourceRoot, todoBinding, {
|
|
696
|
+
createRequestSha256,
|
|
697
|
+
beforeCommit: () => assertState(pinnedRoot, sourceRoot, sourceBinding),
|
|
698
|
+
})
|
|
699
|
+
: [];
|
|
700
|
+
const recovered = recoveryReceipts.map(({ transaction_id }) => transaction_id);
|
|
137
701
|
assertState(pinnedRoot, sourceRoot, sourceBinding);
|
|
138
702
|
context.assertValid();
|
|
139
703
|
const entities = relevant(pinnedRoot, sourceRoot, artifact, undefined, sourceBinding);
|
|
140
704
|
context.assertValid();
|
|
705
|
+
if (artifact === "todo") {
|
|
706
|
+
const recoveredCreate = recoveryReceipts.find((receipt) => receipt.create);
|
|
707
|
+
if (recoveredCreate?.create) {
|
|
708
|
+
const created = selectedById(entities, "todo", recoveredCreate.create.created_id);
|
|
709
|
+
return {
|
|
710
|
+
...envelope("state todo create", { id: created.id, path: created.path, replay: true }, "todo", created.record, false),
|
|
711
|
+
reconciliation: { transaction_id: recoveredCreate.transaction_id, targets: 0, recovered },
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
const publicFile = todoPublicPath(pinnedRoot, sourceRoot);
|
|
715
|
+
const publicRelative = todoBinding.publicPath;
|
|
716
|
+
const publicExists = fs.existsSync(publicFile);
|
|
717
|
+
const loadedMarkdown = readTodoMarkdown(publicFile);
|
|
718
|
+
const markdownBefore = loadedMarkdown.bytes;
|
|
719
|
+
const markdown = loadedMarkdown.text;
|
|
720
|
+
const todoEntities = entities.filter(({ boundary }) => boundary === TODO.boundary);
|
|
721
|
+
const loadedActivation = loadTodoReconciliationActivation(pinnedRoot);
|
|
722
|
+
const activation = loadedActivation?.record ?? null;
|
|
723
|
+
const activating = activation === null;
|
|
724
|
+
const scan = managedRows(markdown, activation, todoEntities);
|
|
725
|
+
const rows = scan.rows;
|
|
726
|
+
const reconciled = reconcileTodoRecords(todoEntities, rows, activating);
|
|
727
|
+
let id;
|
|
728
|
+
let requested;
|
|
729
|
+
let selected;
|
|
730
|
+
if (req.spec.verb === "create") {
|
|
731
|
+
if (req.input) {
|
|
732
|
+
const inputViolations = todoInputViolations(req.input, "create");
|
|
733
|
+
if (inputViolations.length)
|
|
734
|
+
reject({ class: "schema_violation", message: "todo create input is invalid", violations: inputViolations, recovery: todoReadinessRecovery(req.spec.verb) });
|
|
735
|
+
}
|
|
736
|
+
id = allocateEntityId(context.pinnedPath(), options.candidate, sourceRoot);
|
|
737
|
+
requested = createRequest;
|
|
738
|
+
reconciled.records.set(id, requested);
|
|
739
|
+
reconciled.visible.add(id);
|
|
740
|
+
}
|
|
741
|
+
else {
|
|
742
|
+
id = String(req.values.id ?? "");
|
|
743
|
+
if (!ID.test(id))
|
|
744
|
+
reject({ class: "invalid_request", message: `todo ID '${id}' must be ten lowercase letters`, recovery: "Use a bare todo ID returned by create or list; numeric, prefixed, composite, path, and alias identities are invalid." });
|
|
745
|
+
selected = selectedById(todoEntities, "todo", id);
|
|
746
|
+
if (req.input && req.spec.verb === "update") {
|
|
747
|
+
const inputViolations = todoInputViolations(req.input, "update");
|
|
748
|
+
if (inputViolations.length)
|
|
749
|
+
reject({ class: "schema_violation", message: "todo update input is invalid", violations: inputViolations, recovery: todoReadinessRecovery(req.spec.verb) });
|
|
750
|
+
}
|
|
751
|
+
requested = mutationRecord(req, reconciled.records.get(id), todoEntities.map((entity) => ({ ...entity, record: reconciled.records.get(entity.id) })));
|
|
752
|
+
reconciled.records.set(id, requested);
|
|
753
|
+
if (requested.status === "open" || rows.has(id))
|
|
754
|
+
reconciled.visible.add(id);
|
|
755
|
+
}
|
|
756
|
+
const referenceEntities = [...reconciled.records].map(([todoId, record]) => ({ boundary: TODO.boundary, id: todoId, record }));
|
|
757
|
+
for (const [todoId, record] of reconciled.records) {
|
|
758
|
+
const violations = recordViolations("todo", record, sourceRoot);
|
|
759
|
+
if (violations.length)
|
|
760
|
+
reject({ class: "schema_violation", message: "todo entity input is invalid", violations, ...(record.readiness !== undefined ? { recovery: todoReadinessRecovery(req.spec.verb) } : {}) });
|
|
761
|
+
if (record.readiness !== undefined)
|
|
762
|
+
assertTodoReferences(todoId, record, referenceEntities);
|
|
763
|
+
}
|
|
764
|
+
const visibleRecords = new Map([...reconciled.records].filter(([todoId]) => reconciled.visible.has(todoId)));
|
|
765
|
+
const rendered = renderManagedMarkdown(markdown, visibleRecords, rows);
|
|
766
|
+
const activationBytesAfter = activation ? loadedActivation.bytes.toString("utf8") : todoReconciliationActivationBytes(scan.retainedLegacyRows);
|
|
767
|
+
const activationAfter = activation ?? JSON.parse(activationBytesAfter);
|
|
768
|
+
const finalRows = managedRows(rendered, activationAfter, todoEntities).rows;
|
|
769
|
+
for (const [todoId, record] of reconciled.records) {
|
|
770
|
+
const row = finalRows.get(todoId);
|
|
771
|
+
reconciled.records.set(todoId, withBaseline(record, row ? rowSnapshot(row, record) : { present: false }));
|
|
772
|
+
}
|
|
773
|
+
requested = reconciled.records.get(id);
|
|
774
|
+
const targets = todoEntities.map((entity) => ({ path: entity.relativePath, before: exactDiscoveredEntityBytes(entity), after: canonicalEntityEnvelopeBytes({ id: entity.id, artifact: "todo", record: reconciled.records.get(entity.id), migrationProvenance: entity.migrationProvenance ?? undefined }) }));
|
|
775
|
+
if (!selected)
|
|
776
|
+
targets.push({ path: relative(req.projectRoot, targetPath(req.projectRoot, sourceRoot, "todo", id)), before: null, after: canonicalEntityEnvelopeBytes({ id, artifact: "todo", record: requested }) });
|
|
777
|
+
if (activating)
|
|
778
|
+
targets.push({ path: TODO_RECONCILIATION_ACTIVATION_PATH, before: null, after: activationBytesAfter });
|
|
779
|
+
targets.push({ path: publicRelative, before: publicExists ? markdownBefore : null, after: rendered });
|
|
780
|
+
const changed = targets.some((target) => target.before === null || !target.before.equals(Buffer.from(target.after)));
|
|
781
|
+
if (req.dryRun)
|
|
782
|
+
return { ...envelope(`state todo ${req.spec.verb}`, { id, path: targetPath(req.projectRoot, sourceRoot, "todo", id), replay: !changed }, "todo", requested, true), reconciliation: { transaction_id: null, targets: targets.length, recovered } };
|
|
783
|
+
const transaction = publishTodoReconciliation(context, sourceRoot, todoBinding, targets, {
|
|
784
|
+
...(req.spec.verb === "create" ? { create: { created_id: id, request_sha256: createRequestSha256 } } : {}),
|
|
785
|
+
interruptAfterTarget: options.interruptAfterTarget,
|
|
786
|
+
beforeCommit: () => {
|
|
787
|
+
assertState(pinnedRoot, sourceRoot, sourceBinding);
|
|
788
|
+
const currentBinding = todoReconciliationBinding(req.projectRoot, sourceRoot);
|
|
789
|
+
if (currentBinding.publicPath !== todoBinding.publicPath || currentBinding.mappingSha256 !== todoBinding.mappingSha256)
|
|
790
|
+
reject({
|
|
791
|
+
class: "conflict",
|
|
792
|
+
message: "TODO reconciliation mapping changed during transaction publication",
|
|
793
|
+
recovery: "Preserve the changed docs mapping and retry the exact TODO mutation after every transaction target is restored; no mapping bytes were overwritten.",
|
|
794
|
+
});
|
|
795
|
+
const currentActivation = fs.existsSync(path.join(pinnedRoot, TODO_RECONCILIATION_ACTIVATION_PATH))
|
|
796
|
+
? fs.readFileSync(path.join(pinnedRoot, TODO_RECONCILIATION_ACTIVATION_PATH))
|
|
797
|
+
: null;
|
|
798
|
+
if (!currentActivation?.equals(Buffer.from(activationBytesAfter)))
|
|
799
|
+
reject({
|
|
800
|
+
class: "conflict",
|
|
801
|
+
message: "TODO reconciliation activation changed during transaction publication",
|
|
802
|
+
recovery: `Preserve '${TODO_RECONCILIATION_ACTIVATION_PATH}' and retry the exact TODO mutation after every transaction target is restored; no competing activation bytes were overwritten.`,
|
|
803
|
+
});
|
|
804
|
+
},
|
|
805
|
+
});
|
|
806
|
+
context.assertValid();
|
|
807
|
+
return { ...envelope(`state todo ${req.spec.verb}`, { id, path: targetPath(req.projectRoot, sourceRoot, "todo", id), replay: !changed && recovered.length === 0 }, "todo", requested, false), reconciliation: { transaction_id: transaction.id, targets: transaction.targetCount, recovered } };
|
|
808
|
+
}
|
|
141
809
|
if (req.spec.verb === "create") {
|
|
142
|
-
const record = mutationRecord(req);
|
|
143
|
-
const violations = recordViolations(
|
|
810
|
+
const record = mutationRecord(req, undefined, entities);
|
|
811
|
+
const violations = recordViolations("docs", record, sourceRoot);
|
|
144
812
|
if (violations.length)
|
|
145
|
-
reject({ class: "schema_violation", message:
|
|
813
|
+
reject({ class: "schema_violation", message: "docs entity input is invalid", violations });
|
|
146
814
|
const id = allocateEntityId(context.pinnedPath(), options.candidate, sourceRoot);
|
|
147
|
-
if (artifact === "todo" && mapping(req.values.readiness))
|
|
148
|
-
assertTodoReferences(id, record, entities);
|
|
149
815
|
if (req.dryRun)
|
|
150
|
-
return envelope(
|
|
816
|
+
return envelope("state docs create", { id, path: targetPath(req.projectRoot, sourceRoot, "docs", id), replay: false }, "docs", record, true);
|
|
151
817
|
let published;
|
|
152
818
|
try {
|
|
153
|
-
const result = publishEntityUnderLock({ projectRoot: req.projectRoot, sourceRoot, publicationContext: context, artifact, boundary:
|
|
819
|
+
const result = publishEntityUnderLock({ projectRoot: req.projectRoot, sourceRoot, publicationContext: context, artifact: "docs", boundary: DOCS.boundary, id, record });
|
|
154
820
|
published = result;
|
|
155
821
|
assertState(pinnedRoot, sourceRoot, sourceBinding);
|
|
156
822
|
context.assertValid();
|
|
157
|
-
return envelope(
|
|
823
|
+
return envelope("state docs create", result, "docs", record, false);
|
|
158
824
|
}
|
|
159
825
|
catch (error) {
|
|
160
826
|
if (published?.publishedIdentity)
|
|
161
|
-
context.removeExact(relative(req.projectRoot, published.path), published.publishedIdentity);
|
|
827
|
+
context.removeExact(relative(req.projectRoot, published.path), published.publishedIdentity, false);
|
|
162
828
|
throw error;
|
|
163
829
|
}
|
|
164
830
|
}
|
|
@@ -166,12 +832,10 @@ export function mutateTodoDocsEntity(req, options = {}) {
|
|
|
166
832
|
if (!ID.test(id))
|
|
167
833
|
reject({ class: "invalid_request", message: `${artifact} ID '${id}' must be ten lowercase letters`, recovery: `Use a bare ${artifact} ID returned by create or list; numeric, prefixed, composite, path, and alias identities are invalid.` });
|
|
168
834
|
const entity = selectedById(entities, artifact, id);
|
|
169
|
-
const record = mutationRecord(req, entity.record);
|
|
170
|
-
const violations = recordViolations(
|
|
835
|
+
const record = mutationRecord(req, entity.record, entities);
|
|
836
|
+
const violations = recordViolations("docs", record, sourceRoot);
|
|
171
837
|
if (violations.length)
|
|
172
|
-
reject({ class: "schema_violation", message:
|
|
173
|
-
if (artifact === "todo" && mapping(req.values.readiness))
|
|
174
|
-
assertTodoReferences(id, record, entities);
|
|
838
|
+
reject({ class: "schema_violation", message: "docs entity input is invalid", violations });
|
|
175
839
|
if (canonicalRecordJson(record) === canonicalRecordJson(entity.record))
|
|
176
840
|
return envelope(`state ${artifact} ${req.spec.verb}`, { id, path: entity.path, replay: true }, artifact, record, req.dryRun);
|
|
177
841
|
if (req.dryRun)
|
|
@@ -192,75 +856,131 @@ export function mutateTodoDocsEntity(req, options = {}) {
|
|
|
192
856
|
}
|
|
193
857
|
});
|
|
194
858
|
}
|
|
195
|
-
function entry(root, entity
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
859
|
+
function entry(root, entity, row, queueRank, actionability, reconciliation) {
|
|
860
|
+
const todo = entity.boundary === TODO.boundary;
|
|
861
|
+
const itemDrift = todo && reconciliation && Array.isArray(reconciliation.items)
|
|
862
|
+
? reconciliation.items.find((item) => mapping(item) && item.id === entity.id)
|
|
863
|
+
: undefined;
|
|
864
|
+
return {
|
|
865
|
+
id: entity.id,
|
|
866
|
+
artifact: entity.artifact,
|
|
867
|
+
record: todo ? publicReadRecord(entity.record, row) : entity.record,
|
|
868
|
+
...(todo ? { public: publicReadMetadata(entity.record, row) } : {}),
|
|
869
|
+
...(todo ? { public_order: row?.snapshot.order ?? null } : {}),
|
|
870
|
+
...(todo ? { readiness: { state: String(publicReadRecord(entity.record, row).status), blocked: actionability?.outcome === "blocked" } } : {}),
|
|
871
|
+
...(todo && actionability ? { actionability: { outcome: actionability.outcome, eligible: actionability.eligible } } : {}),
|
|
872
|
+
...(todo && queueRank !== undefined ? { queue_rank: queueRank } : {}),
|
|
873
|
+
...(todo && reconciliation ? { reconciliation: { status: String(reconciliation.status), item_status: mapping(itemDrift) ? String(itemDrift.state) : "clean", drifted: Boolean(itemDrift) } } : {}),
|
|
874
|
+
provenance: { storage: "canonical_entity_file", path: relative(root, entity.path), immutable: false },
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
function todoActionability(entities, view, sourceRoot) {
|
|
878
|
+
const projected = view.drift.status !== "inactive";
|
|
879
|
+
const evaluations = evaluateTodoReadinessQueue(entities.map((entity) => {
|
|
880
|
+
const row = view.rows.get(entity.id);
|
|
881
|
+
return {
|
|
882
|
+
id: entity.id,
|
|
883
|
+
artifact: entity.artifact,
|
|
884
|
+
record: publicReadRecord(entity.record, row),
|
|
885
|
+
...(projected ? { projectedOrder: row?.snapshot.order === undefined ? { kind: "absent" } : { kind: "managed", markdownOrder: row.snapshot.order } } : {}),
|
|
886
|
+
};
|
|
887
|
+
}), sourceRoot).evaluations;
|
|
888
|
+
return new Map(evaluations.map((value) => [value.id, value]));
|
|
889
|
+
}
|
|
890
|
+
function snapshot(root, entities, rows) { return createHash("sha256").update(canonicalRecordJson(entities.map((entity) => ({ id: entity.id, boundary: entity.boundary, path: relative(root, entity.path), record: entity.record, ...(entity.id && rows?.has(entity.id) ? { public: rowSnapshot(rows.get(entity.id), entity.record) } : {}) })).sort((a, b) => canonicalRecordJson(a).localeCompare(canonicalRecordJson(b))))).digest("hex"); }
|
|
891
|
+
function decodeCursor(token, root, authorityPath, artifact, restart) { try {
|
|
892
|
+
return decodeListCursor(token, root, authorityPath);
|
|
212
893
|
}
|
|
213
894
|
catch {
|
|
214
|
-
throw failure("cursor_invalid", artifact, `${artifact} cursor is malformed or belongs to another project`,
|
|
895
|
+
throw failure("cursor_invalid", artifact, `${artifact} cursor is malformed or belongs to another project`, restart);
|
|
215
896
|
} }
|
|
216
|
-
function sorted(artifact, entities) {
|
|
897
|
+
function sorted(artifact, entities, rows) {
|
|
217
898
|
const selected = entities.filter(({ boundary }) => boundary === definition(artifact).boundary);
|
|
218
899
|
if (artifact === "docs")
|
|
219
900
|
return selected.sort((a, b) => String(a.record.path).localeCompare(String(b.record.path)) || a.id.localeCompare(b.id));
|
|
220
|
-
return selected.sort((a, b) =>
|
|
901
|
+
return selected.sort((a, b) => {
|
|
902
|
+
const left = publicReadRecord(a.record, rows?.get(a.id));
|
|
903
|
+
const right = publicReadRecord(b.record, rows?.get(b.id));
|
|
904
|
+
return TODO_SEVERITIES.indexOf(left.severity) - TODO_SEVERITIES.indexOf(right.severity)
|
|
905
|
+
|| TODO_STATUSES.indexOf(left.status) - TODO_STATUSES.indexOf(right.status)
|
|
906
|
+
|| (rows?.get(a.id)?.snapshot.order ?? Number.MAX_SAFE_INTEGER) - (rows?.get(b.id)?.snapshot.order ?? Number.MAX_SAFE_INTEGER)
|
|
907
|
+
|| a.id.localeCompare(b.id);
|
|
908
|
+
});
|
|
221
909
|
}
|
|
222
910
|
export function getTodoDocsEntity(root, artifact, id, sourceRoot = resolveSourceRoot()) {
|
|
223
|
-
|
|
224
|
-
|
|
911
|
+
if (artifact === "todo")
|
|
912
|
+
assertTodoReconciliationReadable(root, sourceRoot, id);
|
|
913
|
+
const entities = relevant(root, sourceRoot, artifact);
|
|
914
|
+
const entity = selectedById(entities, artifact, id);
|
|
915
|
+
const view = artifact === "todo" ? inspectTodoReadView(root, sourceRoot, entities) : null;
|
|
916
|
+
const actionability = view ? todoActionability(sorted("todo", entities, view.rows), view, sourceRoot).get(id) : undefined;
|
|
917
|
+
return { schemaVersion: "agentera.stateGet.v1", command: `state ${artifact} get`, status: "ok", entry: entry(root, entity, view?.rows.get(id), undefined, actionability, view?.drift), ...(view ? { reconciliation: view.drift } : {}), source_contract: { authority: "references/artifacts/state-storage-authority.yaml", detail: "full_entity" } };
|
|
225
918
|
}
|
|
226
919
|
export function listTodoDocsEntities(root, artifact, limit, cursor, filters = {}, options = {}) {
|
|
227
920
|
const sourceRoot = options.sourceRoot ?? resolveSourceRoot();
|
|
228
921
|
const declared = contract(definition(artifact).boundary, sourceRoot);
|
|
229
922
|
const take = limit ?? declared.defaultLimit;
|
|
923
|
+
if (artifact === "todo")
|
|
924
|
+
assertTodoReconciliationReadable(root, sourceRoot);
|
|
230
925
|
if (!Number.isSafeInteger(take) || take < 1 || take > declared.maximumLimit)
|
|
231
926
|
throw failure("invalid_request", artifact, `${artifact} list limit must be 1..${declared.maximumLimit}`, "Use a limit in the declared range.", undefined, 2);
|
|
232
927
|
const all = relevant(root, sourceRoot, artifact, options.discovery).filter(({ boundary }) => boundary === definition(artifact).boundary);
|
|
233
|
-
|
|
928
|
+
const view = artifact === "todo" ? inspectTodoReadView(root, sourceRoot, all) : null;
|
|
929
|
+
let selected = sorted(artifact, all, view?.rows);
|
|
930
|
+
const queueRanks = artifact === "todo" ? new Map(selected.map((entity, index) => [entity.id, index + 1])) : new Map();
|
|
931
|
+
const actionability = artifact === "todo" ? todoActionability(selected, view, sourceRoot) : new Map();
|
|
234
932
|
if (artifact === "todo")
|
|
235
|
-
selected = selected.filter((entity) => (!filters.severity ||
|
|
933
|
+
selected = selected.filter((entity) => { const record = publicReadRecord(entity.record, view?.rows.get(entity.id)); return (!filters.severity || record.severity === filters.severity) && (!filters.status || record.status === filters.status); });
|
|
236
934
|
if (artifact === "docs")
|
|
237
935
|
selected = selected.filter((entity) => (!filters.status || entity.record.status === filters.status) && (!filters.topic || [entity.record.document, entity.record.path, entity.record.status].some((value) => String(value).toLowerCase().includes(String(filters.topic).toLowerCase()))));
|
|
238
|
-
const
|
|
239
|
-
let start = 0;
|
|
240
|
-
if (cursor) {
|
|
241
|
-
const value = decode(cursor, root, declared.authorityPath, artifact);
|
|
242
|
-
if (value.snapshot_id !== snap || value.order !== definition(artifact).order || canonicalRecordJson(value.filters) !== canonicalRecordJson(filters))
|
|
243
|
-
throw failure("cursor_snapshot_unavailable", artifact, `${artifact} state changed after this cursor snapshot`, "Omit --cursor to restart from current state.");
|
|
244
|
-
const found = selected.findIndex(({ id }) => id === value.after);
|
|
245
|
-
if (found < 0)
|
|
246
|
-
throw failure("cursor_snapshot_unavailable", artifact, `${artifact} cursor continuation is unavailable`, "Omit --cursor to restart.");
|
|
247
|
-
start = found + 1;
|
|
248
|
-
}
|
|
249
|
-
let page = selected.slice(start, start + take);
|
|
250
|
-
let trimmed = false;
|
|
251
|
-
const response = () => { const remaining = selected.length - start - page.length; const next = remaining && page.length ? encode({ snapshot_id: snap, order: definition(artifact).order, filters, after: page.at(-1).id }, root, declared.authorityPath) : undefined; const filterFlags = Object.entries(filters).map(([name, value]) => ` --${name} ${shell(value)}`).join(""); return { schemaVersion: "agentera.stateList.v1", command: `state ${artifact} list`, status: remaining ? "degraded" : "ok", entries: page.map((entity) => entry(root, entity)), counts: { total: selected.length, returned: page.length, remaining }, order: definition(artifact).order, filters, snapshot: { id: snap, first_page: !cursor, has_more: Boolean(remaining), candidate_count: selected.length }, source: { artifact, authority: "canonical_entity_files", root: declared.entityRoot }, source_contract: { authority: "references/artifacts/state-storage-authority.yaml", detail: "full", cursor: "opaque_snapshot_cursor" }, retrieval: { get: `agentera state ${artifact} get --id ID --format json`, ...(next ? { continue: `agentera state ${artifact} list${filterFlags} --limit ${take} --cursor ${next} --format json` } : {}) }, ...(remaining ? { omitted: true, omitted_count: remaining, omission_reason: trimmed ? "serialized_byte_budget" : "page_limit", next_cursor: next } : {}) }; };
|
|
936
|
+
const format = options.format ?? "json";
|
|
252
937
|
const outputBudget = declared.maxUtf8Bytes - (options.reservedUtf8Bytes ?? 0);
|
|
253
938
|
if (outputBudget < 1024)
|
|
254
939
|
throw failure("unsupported_state", artifact, `${artifact} singleton metadata leaves no room for a bounded entity view`, "Reduce the authority-owned singleton metadata within its declared artifact budget.");
|
|
255
|
-
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
940
|
+
const projectionOptions = { family: artifact, artifact, boundary: definition(artifact).boundary, format, maxUtf8Bytes: outputBudget, selector: options.selector };
|
|
941
|
+
const selector = resolveEntityListSelector(options.selector, selected.map((entity) => entry(root, entity, view?.rows.get(entity.id), queueRanks.get(entity.id), actionability.get(entity.id), view?.drift)), projectionOptions);
|
|
942
|
+
const selectorKey = entityListSelectorKey(selector);
|
|
943
|
+
const filterOrder = artifact === "todo" ? ["severity", "status"] : ["topic", "status"];
|
|
944
|
+
const filterFlags = filterOrder.flatMap((name) => filters[name] === undefined ? [] : [` --${name} ${shellQuoteArgument(filters[name])}`]).join("");
|
|
945
|
+
const selectorFlags = entityListSelectorFlags(selector);
|
|
946
|
+
const restart = `agentera state ${artifact} list${filterFlags}${selectorFlags} --limit ${take} --format json`;
|
|
947
|
+
const snap = snapshot(root, all, view?.rows);
|
|
948
|
+
let start = 0;
|
|
949
|
+
if (cursor) {
|
|
950
|
+
const value = decodeCursor(cursor, root, declared.authorityPath, artifact, restart);
|
|
951
|
+
if (artifact === "todo") {
|
|
952
|
+
if (value.limit === undefined)
|
|
953
|
+
throw failure("cursor_invalid", artifact, "todo cursor lacks the required effective limit binding", restart);
|
|
954
|
+
if (!Number.isSafeInteger(value.limit) || Number(value.limit) < 1 || Number(value.limit) > declared.maximumLimit)
|
|
955
|
+
throw failure("cursor_invalid", artifact, "todo cursor has an invalid effective limit binding", restart);
|
|
956
|
+
if (value.limit !== take)
|
|
957
|
+
throw failure("cursor_invalid", artifact, `todo cursor is bound to --limit ${value.limit}, not --limit ${take}`, restart);
|
|
958
|
+
if (value.selector !== selectorKey)
|
|
959
|
+
throw failure("cursor_invalid", artifact, "todo cursor selectors do not match this request", restart);
|
|
960
|
+
if (value.order !== TODO.order)
|
|
961
|
+
throw failure("cursor_invalid", artifact, "todo cursor order does not match this request", restart);
|
|
962
|
+
if (canonicalRecordJson(value.filters) !== canonicalRecordJson(filters))
|
|
963
|
+
throw failure("cursor_invalid", artifact, "todo cursor filters do not match this request", restart);
|
|
964
|
+
if (value.snapshot_id !== snap)
|
|
965
|
+
throw failure("cursor_snapshot_unavailable", artifact, "todo cursor snapshot is no longer available", restart);
|
|
966
|
+
const found = selected.findIndex(({ id }) => id === value.after);
|
|
967
|
+
if (found < 0)
|
|
968
|
+
throw failure("cursor_snapshot_unavailable", artifact, "todo cursor continuation identity is no longer available", restart);
|
|
969
|
+
start = found + 1;
|
|
970
|
+
}
|
|
971
|
+
else {
|
|
972
|
+
if (value.selector !== selectorKey)
|
|
973
|
+
throw failure("cursor_invalid", artifact, `${artifact} cursor selectors do not match this request`, "Repeat the original selector, or omit --cursor to restart from current state.");
|
|
974
|
+
if (value.snapshot_id !== snap || value.order !== DOCS.order || canonicalRecordJson(value.filters) !== canonicalRecordJson(filters))
|
|
975
|
+
throw failure("cursor_snapshot_unavailable", artifact, `${artifact} state or filters changed after this cursor snapshot`, "Repeat the original filters, or omit --cursor to restart from current state.");
|
|
976
|
+
const found = selected.findIndex(({ id }) => id === value.after);
|
|
977
|
+
if (found < 0)
|
|
978
|
+
throw failure("cursor_snapshot_unavailable", artifact, `${artifact} cursor continuation is unavailable`, "Omit --cursor to restart.");
|
|
979
|
+
start = found + 1;
|
|
980
|
+
}
|
|
261
981
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
return
|
|
982
|
+
const page = selected.slice(start, start + take);
|
|
983
|
+
const response = () => { const remaining = selected.length - start - page.length; const next = remaining && page.length ? encodeListCursor({ snapshot_id: snap, order: definition(artifact).order, filters, selector: selectorKey, after: page.at(-1).id, ...(artifact === "todo" ? { limit: take } : {}) }, root, declared.authorityPath) : undefined; return { schemaVersion: "agentera.stateList.v1", command: `state ${artifact} list`, status: remaining ? "degraded" : "ok", entries: page.map((entity) => entry(root, entity, view?.rows.get(entity.id), queueRanks.get(entity.id), actionability.get(entity.id), view?.drift)), ...(view ? { reconciliation: view.drift } : {}), counts: { total: selected.length, returned: page.length, remaining }, order: definition(artifact).order, filters, snapshot: { id: snap, first_page: !cursor, has_more: Boolean(remaining), candidate_count: selected.length }, source: { artifact, authority: "canonical_entity_files", root: declared.entityRoot }, source_contract: { authority: "references/artifacts/state-storage-authority.yaml", detail: "full", cursor: "opaque_snapshot_cursor" }, retrieval: { ...(next ? { continue: `agentera state ${artifact} list${filterFlags}${selectorFlags} --limit ${take} --cursor ${next} --format json` } : {}) }, ...(remaining ? { omitted: true, omitted_count: remaining, omission_reason: "page_limit", next_cursor: next } : {}) }; };
|
|
984
|
+
return projectEntityList(response(), selector, projectionOptions);
|
|
265
985
|
}
|
|
266
986
|
//# sourceMappingURL=todoDocsEntities.js.map
|