apsimo 1.3.0__py3-none-any.whl
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.
- apsimo/__init__.py +38 -0
- apsimo/__main__.py +6 -0
- apsimo/agent/__init__.py +6 -0
- apsimo/agent/client.py +276 -0
- apsimo/agent/models.py +46 -0
- apsimo/agents/__init__.py +20 -0
- apsimo/agents/models.py +264 -0
- apsimo/agents/store.py +861 -0
- apsimo/agents/websocket.py +522 -0
- apsimo/api/__init__.py +1 -0
- apsimo/api/auth_telemetry.py +287 -0
- apsimo/api/authority.py +1203 -0
- apsimo/api/contact_grants.py +347 -0
- apsimo/api/middleware.py +483 -0
- apsimo/api/routers/__init__.py +1 -0
- apsimo/api/routers/commitment_work.py +265 -0
- apsimo/api/routers/context_gate.py +123 -0
- apsimo/api/routers/executions.py +140 -0
- apsimo/api/routers/followup_plans.py +147 -0
- apsimo/api/routers/governed_actions.py +162 -0
- apsimo/api/routers/host.py +14473 -0
- apsimo/api/routers/initiative_work.py +115 -0
- apsimo/api/routers/mining.py +104 -0
- apsimo/api/routers/observations.py +110 -0
- apsimo/api/routers/social_state.py +225 -0
- apsimo/api/routers/task_queue.py +2715 -0
- apsimo/api/routers/temporal_followups.py +251 -0
- apsimo/api/routers/transport.py +110 -0
- apsimo/api/routers/transport_ingress_api.py +240 -0
- apsimo/api/schemas/__init__.py +1 -0
- apsimo/api/schemas/host.py +1949 -0
- apsimo/autonomy/cli.py +110 -0
- apsimo/autonomy/condition_worker.py +437 -0
- apsimo/autonomy/config.py +424 -0
- apsimo/autonomy/loop.py +4316 -0
- apsimo/autonomy/registry.py +339 -0
- apsimo/autonomy/scheduler.py +1822 -0
- apsimo/autonomy/synthesis.py +449 -0
- apsimo/backup.py +962 -0
- apsimo/beliefs/__init__.py +23 -0
- apsimo/beliefs/contradictions.py +109 -0
- apsimo/beliefs/decay.py +61 -0
- apsimo/beliefs/engine.py +479 -0
- apsimo/beliefs/models.py +67 -0
- apsimo/beliefs/promotion.py +41 -0
- apsimo/beliefs/resolve.py +58 -0
- apsimo/beliefs/source_claims.py +690 -0
- apsimo/beliefs/source_projection.py +883 -0
- apsimo/beliefs/source_time.py +208 -0
- apsimo/beliefs/store.py +133 -0
- apsimo/briefings/aggregators.py +824 -0
- apsimo/briefings/composer.py +420 -0
- apsimo/briefings/config.py +55 -0
- apsimo/briefings/delivery.py +439 -0
- apsimo/briefings/engagement.py +97 -0
- apsimo/briefings/engine.py +274 -0
- apsimo/briefings/enhancer.py +99 -0
- apsimo/briefings/models.py +183 -0
- apsimo/briefings/scheduler.py +382 -0
- apsimo/briefings/store.py +435 -0
- apsimo/chain/__init__.py +48 -0
- apsimo/chain/block.py +100 -0
- apsimo/chain/cli.py +704 -0
- apsimo/chain/genesis.py +443 -0
- apsimo/chain/identity.py +416 -0
- apsimo/chain/keys.py +1025 -0
- apsimo/chain/local_keys.py +187 -0
- apsimo/chain/manager.py +290 -0
- apsimo/chain/node.py +163 -0
- apsimo/chain/plugin_transactions.py +371 -0
- apsimo/chain/protocol.py +220 -0
- apsimo/chain/state_machine.py +676 -0
- apsimo/chain/storage.py +503 -0
- apsimo/chain/transactions.py +250 -0
- apsimo/chain/validation.py +397 -0
- apsimo/channels/__init__.py +1 -0
- apsimo/channels/manifest.py +31 -0
- apsimo/channels/migrations/001_channels_schema.sql +12 -0
- apsimo/channels/phone_gateways.py +42 -0
- apsimo/channels/presence.py +188 -0
- apsimo/channels/router.py +235 -0
- apsimo/channels/store.py +231 -0
- apsimo/cli.py +2688 -0
- apsimo/cognition/__init__.py +11 -0
- apsimo/cognition/charter.py +398 -0
- apsimo/cognition/drive_governance.py +3530 -0
- apsimo/cognition/evidence_pipeline.py +1627 -0
- apsimo/cognition/external_events.py +932 -0
- apsimo/cognition/goal_spine.py +3488 -0
- apsimo/cognition/introspection.py +214 -0
- apsimo/cognition/prompt.py +150 -0
- apsimo/cognition/runtime.py +108 -0
- apsimo/cognition/trigger.py +154 -0
- apsimo/commitments/__init__.py +18 -0
- apsimo/commitments/local_work.py +355 -0
- apsimo/commitments/store.py +1052 -0
- apsimo/commitments/work.py +91 -0
- apsimo/compat.py +53 -0
- apsimo/compression/__init__.py +467 -0
- apsimo/connectors/__init__.py +21 -0
- apsimo/connectors/base.py +152 -0
- apsimo/connectors/caldav_calendar.py +125 -0
- apsimo/connectors/fs_documents.py +85 -0
- apsimo/connectors/imap_email.py +138 -0
- apsimo/connectors/manager.py +218 -0
- apsimo/connectors/webhook_pull.py +88 -0
- apsimo/contacts/__init__.py +33 -0
- apsimo/contacts/comms.py +357 -0
- apsimo/contacts/config.py +79 -0
- apsimo/contacts/exporters/__init__.py +1 -0
- apsimo/contacts/exporters/vcard.py +71 -0
- apsimo/contacts/identity_links.py +251 -0
- apsimo/contacts/importer.py +280 -0
- apsimo/contacts/importers/__init__.py +1 -0
- apsimo/contacts/importers/batch.py +43 -0
- apsimo/contacts/importers/macos_contacts.py +101 -0
- apsimo/contacts/migrations/001_contacts_schema.sql +141 -0
- apsimo/contacts/migrations/002_trust_scopes.sql +36 -0
- apsimo/contacts/migrations/003_open_gateway_enum.sql +32 -0
- apsimo/contacts/migrations/004_contact_provision_operations.sql +18 -0
- apsimo/contacts/migrations/005_identity_links.sql +27 -0
- apsimo/contacts/models.py +308 -0
- apsimo/contacts/scoring.py +16 -0
- apsimo/contacts/store.py +1623 -0
- apsimo/contacts/transport_ingress.py +252 -0
- apsimo/contacts/world_bridge.py +314 -0
- apsimo/contextgate/__init__.py +69 -0
- apsimo/contextgate/chunker.py +169 -0
- apsimo/contextgate/estimate.py +54 -0
- apsimo/contextgate/gate.py +313 -0
- apsimo/contextgate/retrieve.py +115 -0
- apsimo/delivery/__init__.py +16 -0
- apsimo/delivery/bridge.py +1260 -0
- apsimo/delivery/channels.py +526 -0
- apsimo/delivery/classification.py +50 -0
- apsimo/delivery/rate_limiter.py +268 -0
- apsimo/delivery/reachout_policy.py +206 -0
- apsimo/directed/__init__.py +22 -0
- apsimo/directed/audit.py +167 -0
- apsimo/directed/intake.py +95 -0
- apsimo/directed/models.py +191 -0
- apsimo/directed/service.py +509 -0
- apsimo/directives/__init__.py +25 -0
- apsimo/directives/evidence.py +87 -0
- apsimo/directives/extractor.py +188 -0
- apsimo/directives/guard.py +364 -0
- apsimo/directives/models.py +206 -0
- apsimo/directives/service.py +372 -0
- apsimo/directives/store.py +167 -0
- apsimo/doctor.py +2173 -0
- apsimo/environment.py +43 -0
- apsimo/events/__init__.py +33 -0
- apsimo/events/broadcaster.py +98 -0
- apsimo/events/bus.py +217 -0
- apsimo/events/journal.py +863 -0
- apsimo/events/stream.py +131 -0
- apsimo/events/types.py +150 -0
- apsimo/execution_results.py +357 -0
- apsimo/feedback/__init__.py +5 -0
- apsimo/feedback/store.py +76 -0
- apsimo/feeds/__init__.py +19 -0
- apsimo/feeds/cli.py +84 -0
- apsimo/feeds/engine.py +437 -0
- apsimo/feeds/example-feed.yaml +77 -0
- apsimo/feeds/hermes_cron.py +126 -0
- apsimo/feeds/manager.py +235 -0
- apsimo/feeds/spec.py +250 -0
- apsimo/feeds/template.py +202 -0
- apsimo/gate/__init__.py +18 -0
- apsimo/gate/audit.py +61 -0
- apsimo/gate/communication_policy.py +166 -0
- apsimo/gate/config.py +72 -0
- apsimo/gate/context_provenance.py +170 -0
- apsimo/gate/env_risk.py +226 -0
- apsimo/gate/guard_audit.py +353 -0
- apsimo/gate/layers/__init__.py +1 -0
- apsimo/gate/layers/base.py +15 -0
- apsimo/gate/layers/l1_recipient.py +66 -0
- apsimo/gate/layers/l2_pii.py +134 -0
- apsimo/gate/layers/l3_cross_context.py +50 -0
- apsimo/gate/layers/l4_trust_tier.py +78 -0
- apsimo/gate/layers/l5_injection.py +199 -0
- apsimo/gate/layers/l6_review.py +86 -0
- apsimo/gate/layers/l7_delay.py +100 -0
- apsimo/gate/layers/tom2_epistemic.py +185 -0
- apsimo/gate/models.py +64 -0
- apsimo/gate/pending_dispatch.py +5 -0
- apsimo/gate/pipeline.py +206 -0
- apsimo/gate/rejection.py +259 -0
- apsimo/gate/response_guard.py +700 -0
- apsimo/gate/rulesets/injection_v1.yaml +51 -0
- apsimo/gate/surface_policy.py +189 -0
- apsimo/gate/taint.py +226 -0
- apsimo/genesis.json +9 -0
- apsimo/goals/__init__.py +100 -0
- apsimo/goals/config.py +38 -0
- apsimo/goals/decomposer.py +421 -0
- apsimo/goals/engine.py +617 -0
- apsimo/goals/inference.py +354 -0
- apsimo/goals/models.py +302 -0
- apsimo/goals/priority.py +270 -0
- apsimo/goals/queue_bridge.py +149 -0
- apsimo/goals/replan.py +450 -0
- apsimo/goals/schema.sql +89 -0
- apsimo/goals/store.py +692 -0
- apsimo/governed_actions.py +1708 -0
- apsimo/harness_integration/__init__.py +45 -0
- apsimo/harness_integration/context.py +41 -0
- apsimo/harness_integration/skills.py +231 -0
- apsimo/identity/__init__.py +26 -0
- apsimo/identity/participants.py +181 -0
- apsimo/identity/resolver.py +329 -0
- apsimo/identity_bootstrap/__init__.py +5 -0
- apsimo/identity_bootstrap/builder.py +208 -0
- apsimo/identity_bootstrap/corpus.py +443 -0
- apsimo/identity_bootstrap/models.py +54 -0
- apsimo/identity_bootstrap/runner.py +353 -0
- apsimo/identity_bootstrap/seeders/__init__.py +25 -0
- apsimo/identity_bootstrap/seeders/briefings.py +109 -0
- apsimo/identity_bootstrap/seeders/chain.py +57 -0
- apsimo/identity_bootstrap/seeders/goals.py +128 -0
- apsimo/identity_bootstrap/seeders/memory.py +191 -0
- apsimo/identity_bootstrap/seeders/neo4j_cognition.py +79 -0
- apsimo/identity_bootstrap/seeders/relationship.py +152 -0
- apsimo/identity_bootstrap/seeders/sessions.py +67 -0
- apsimo/identity_bootstrap/seeders/skills.py +92 -0
- apsimo/identity_bootstrap/seeders/task_queue.py +72 -0
- apsimo/identity_bootstrap/seeders/world_model.py +143 -0
- apsimo/identity_bootstrap/self_query.py +92 -0
- apsimo/identity_bootstrap/self_reflection.py +155 -0
- apsimo/identity_bootstrap/skill.py +37 -0
- apsimo/identity_bootstrap/verifier.py +436 -0
- apsimo/initiatives/__init__.py +20 -0
- apsimo/initiatives/action_registry.py +454 -0
- apsimo/initiatives/approval_authority.py +2105 -0
- apsimo/initiatives/approval_policy.py +123 -0
- apsimo/initiatives/assignment.py +263 -0
- apsimo/initiatives/backup_evidence.py +100 -0
- apsimo/initiatives/context_freshness.py +103 -0
- apsimo/initiatives/models.py +318 -0
- apsimo/initiatives/native_work.py +270 -0
- apsimo/initiatives/standing_approvals.py +232 -0
- apsimo/initiatives/store.py +1081 -0
- apsimo/initiatives/temporal_followup.py +410 -0
- apsimo/intelligence/__init__.py +1 -0
- apsimo/intelligence/cognition/__init__.py +24 -0
- apsimo/intelligence/cognition/gap_detector.py +148 -0
- apsimo/intelligence/cognition/metalearner.py +547 -0
- apsimo/intelligence/cognition/metrics_collector.py +217 -0
- apsimo/intelligence/cognition/performance_index.py +299 -0
- apsimo/intelligence/cognition/registry.py +192 -0
- apsimo/intelligence/cognition/strategy_adjuster.py +222 -0
- apsimo/intelligence/cognition/types.py +16 -0
- apsimo/intelligence/components/__init__.py +66 -0
- apsimo/intelligence/components/anomaly_detector.py +413 -0
- apsimo/intelligence/components/initiative_engine.py +2643 -0
- apsimo/intelligence/components/preference_learner.py +521 -0
- apsimo/intelligence/components/research_orchestrator.py +358 -0
- apsimo/intelligence/components/self_directed_thinker.py +221 -0
- apsimo/intelligence/components/self_reflector.py +252 -0
- apsimo/intelligence/components/session_continuity.py +154 -0
- apsimo/intelligence/components/task_planner.py +320 -0
- apsimo/intelligence/components/tool_learner.py +217 -0
- apsimo/intelligence/graph/__init__.py +79 -0
- apsimo/intelligence/graph/client.py +2483 -0
- apsimo/intelligence/graph/consolidator.py +405 -0
- apsimo/intelligence/graph/distiller.py +312 -0
- apsimo/intelligence/graph/migrations.py +129 -0
- apsimo/intelligence/graph/queries.py +248 -0
- apsimo/intelligence/graph/recall.py +281 -0
- apsimo/intelligence/graph/reconciler.py +144 -0
- apsimo/intelligence/graph/schema.py +337 -0
- apsimo/intelligence/graph/selection.py +252 -0
- apsimo/intelligence/learning/__init__.py +17 -0
- apsimo/intelligence/learning/continuous_learner.py +245 -0
- apsimo/intelligence/learning/feedback_store.py +321 -0
- apsimo/intelligence/mind_model/__init__.py +1 -0
- apsimo/intelligence/mind_model/graph_baseline.py +136 -0
- apsimo/intelligence/mind_model/signal_collector.py +361 -0
- apsimo/intelligence/relationships/__init__.py +11 -0
- apsimo/intelligence/relationships/profiler.py +389 -0
- apsimo/intelligence/relationships/scorer.py +560 -0
- apsimo/intelligence/relationships/signal_floor.py +66 -0
- apsimo/intelligence/relationships/trust_tiers.py +300 -0
- apsimo/intelligence/synthesis/__init__.py +40 -0
- apsimo/intelligence/synthesis/connection_discoverer.py +379 -0
- apsimo/intelligence/synthesis/cross_domain_analyzer.py +287 -0
- apsimo/intelligence/synthesis/insight_deliverer.py +171 -0
- apsimo/intelligence/synthesis/insight_store.py +79 -0
- apsimo/intelligence/synthesis/insight_validator.py +183 -0
- apsimo/intelligence/synthesis/novelty_scorer.py +267 -0
- apsimo/intelligence/turn_middleware/__init__.py +15 -0
- apsimo/intelligence/turn_middleware/memory_sync.py +119 -0
- apsimo/mcp/__init__.py +41 -0
- apsimo/mcp/__main__.py +6 -0
- apsimo/mcp/config.py +287 -0
- apsimo/mcp/server.py +501 -0
- apsimo/migrations.py +187 -0
- apsimo/mining/__init__.py +27 -0
- apsimo/mining/corpus.py +239 -0
- apsimo/mining/escalations.py +289 -0
- apsimo/mining/models.py +169 -0
- apsimo/mining/store.py +210 -0
- apsimo/models/__init__.py +30 -0
- apsimo/models/memory.py +80 -0
- apsimo/models/mesh.py +72 -0
- apsimo/models/person.py +104 -0
- apsimo/models/signal.py +108 -0
- apsimo/observations/__init__.py +15 -0
- apsimo/observations/store.py +277 -0
- apsimo/patterns/__init__.py +6 -0
- apsimo/patterns/extract.py +187 -0
- apsimo/patterns/store.py +227 -0
- apsimo/persona/__init__.py +1 -0
- apsimo/persona/engine.py +611 -0
- apsimo/persona/manifest.py +140 -0
- apsimo/projects/__init__.py +28 -0
- apsimo/projects/engine.py +1681 -0
- apsimo/projects/event_outbox.py +188 -0
- apsimo/projects/models.py +216 -0
- apsimo/projects/planner.py +181 -0
- apsimo/projects/store.py +1446 -0
- apsimo/proposals/__init__.py +12 -0
- apsimo/proposals/engine.py +114 -0
- apsimo/proposals/models.py +207 -0
- apsimo/qualification/__init__.py +1 -0
- apsimo/qualification/cases.py +75 -0
- apsimo/qualification/cli.py +51 -0
- apsimo/qualification/memory_cases.py +209 -0
- apsimo/qualification/records.py +92 -0
- apsimo/qualification/report.py +87 -0
- apsimo/qualification/runner.py +311 -0
- apsimo/qualification/structured_cases.py +131 -0
- apsimo/reasoning/__init__.py +13 -0
- apsimo/reasoning/executor.py +506 -0
- apsimo/reasoning/loop.py +373 -0
- apsimo/reasoning/native_tools/__init__.py +16 -0
- apsimo/reasoning/native_tools/calculate.py +141 -0
- apsimo/reasoning/native_tools/file_ops.py +150 -0
- apsimo/reasoning/native_tools/web_search.py +49 -0
- apsimo/reasoning/tool_policy.py +182 -0
- apsimo/redact/__init__.py +176 -0
- apsimo/repos/__init__.py +5 -0
- apsimo/repos/mirrors.py +204 -0
- apsimo/research/__init__.py +41 -0
- apsimo/research/artifact.py +482 -0
- apsimo/research/gatherer.py +387 -0
- apsimo/research/pipeline.py +513 -0
- apsimo/research/search/__init__.py +7 -0
- apsimo/research/search/base.py +41 -0
- apsimo/research/search/brave.py +59 -0
- apsimo/research/search/cache.py +51 -0
- apsimo/research/search/duckduckgo.py +103 -0
- apsimo/research/search/orchestrator.py +119 -0
- apsimo/research/search/serpapi.py +59 -0
- apsimo/research/search/tavily.py +59 -0
- apsimo/research/synthesizer.py +309 -0
- apsimo/router/__init__.py +30 -0
- apsimo/router/complexity_scorer.py +148 -0
- apsimo/router/endpoints.py +153 -0
- apsimo/router/fallback.py +58 -0
- apsimo/router/functions.py +243 -0
- apsimo/router/native_policy.py +52 -0
- apsimo/router/router.py +762 -0
- apsimo/router/self_learning.py +174 -0
- apsimo/router/tiers.py +677 -0
- apsimo/sandbox/__init__.py +21 -0
- apsimo/sandbox/backend.py +195 -0
- apsimo/sandbox/manager.py +173 -0
- apsimo/scope_bounds.py +7 -0
- apsimo/secrets/__init__.py +6 -0
- apsimo/secrets/backends/__init__.py +8 -0
- apsimo/secrets/backends/base.py +42 -0
- apsimo/secrets/backends/env.py +110 -0
- apsimo/secrets/backends/keyring.py +72 -0
- apsimo/secrets/backends/onepassword.py +232 -0
- apsimo/secrets/cli.py +191 -0
- apsimo/secrets/manager.py +160 -0
- apsimo/secrets/migration.py +101 -0
- apsimo/secrets/types.py +98 -0
- apsimo/seed.py +41 -0
- apsimo/self_model/__init__.py +37 -0
- apsimo/self_model/appraisals.py +673 -0
- apsimo/self_model/benchmark.py +1314 -0
- apsimo/self_model/brief.py +40 -0
- apsimo/self_model/event_concerns.py +1128 -0
- apsimo/self_model/execution_forecasts.py +353 -0
- apsimo/self_model/expectations.py +1595 -0
- apsimo/self_model/experiments.py +1150 -0
- apsimo/self_model/journal.py +148 -0
- apsimo/self_model/judgments.py +705 -0
- apsimo/self_model/native_outcomes.py +55 -0
- apsimo/self_model/params.py +220 -0
- apsimo/self_model/perspective.py +246 -0
- apsimo/self_model/reconcile.py +183 -0
- apsimo/self_model/reply_forecasts.py +381 -0
- apsimo/self_model/runtime_forecasts.py +296 -0
- apsimo/self_model/runtime_models.py +67 -0
- apsimo/self_model/settlement.py +207 -0
- apsimo/self_model/situation.py +1731 -0
- apsimo/self_model/store.py +883 -0
- apsimo/self_model/supervised.py +137 -0
- apsimo/self_model/thinker.py +99 -0
- apsimo/self_model/trust.py +388 -0
- apsimo/self_model/workspace.py +2388 -0
- apsimo/server.py +4197 -0
- apsimo/services/__init__.py +1 -0
- apsimo/services/agent_bridge.py +474 -0
- apsimo/services/initiative_executor.py +914 -0
- apsimo/services/instance.py +297 -0
- apsimo/sessions/__init__.py +22 -0
- apsimo/sessions/config.py +13 -0
- apsimo/sessions/context_loader.py +88 -0
- apsimo/sessions/federation_session.py +75 -0
- apsimo/sessions/isolated_session.py +98 -0
- apsimo/sessions/reports.py +84 -0
- apsimo/sessions/store.py +148 -0
- apsimo/setup.py +2818 -0
- apsimo/setup_hermes.py +879 -0
- apsimo/setup_local_work.py +218 -0
- apsimo/setup_native_goals.py +134 -0
- apsimo/setup_native_reviews.py +115 -0
- apsimo/skills/__init__.py +10 -0
- apsimo/skills/base.py +108 -0
- apsimo/skills/budget.py +28 -0
- apsimo/skills/executor.py +493 -0
- apsimo/skills/executors/__init__.py +1 -0
- apsimo/skills/executors/behavioral_correction.py +75 -0
- apsimo/skills/executors/capability_gap.py +38 -0
- apsimo/skills/executors/data_quality.py +163 -0
- apsimo/skills/executors/knowledge_acquisition.py +41 -0
- apsimo/skills/executors/operational_hygiene.py +185 -0
- apsimo/skills/executors/subsystem_health.py +169 -0
- apsimo/skills/hermes_export.py +431 -0
- apsimo/skills/index.py +123 -0
- apsimo/skills/learning/__init__.py +21 -0
- apsimo/skills/learning/novelty_detector.py +206 -0
- apsimo/skills/learning/pattern_extractor.py +199 -0
- apsimo/skills/learning/triggers.py +159 -0
- apsimo/skills/loader.py +246 -0
- apsimo/skills/migrations/002_progressive_loading.sql +6 -0
- apsimo/skills/migrations/backfill_triggers.py +20 -0
- apsimo/skills/models.py +202 -0
- apsimo/skills/packager.py +128 -0
- apsimo/skills/protocols.py +70 -0
- apsimo/skills/registry.py +191 -0
- apsimo/skills/runtime.py +58 -0
- apsimo/skills/sandbox_runner.py +229 -0
- apsimo/skills/scheduler.py +129 -0
- apsimo/skills/schema.py +79 -0
- apsimo/skills/security/__init__.py +12 -0
- apsimo/skills/security/guards.py +53 -0
- apsimo/skills/security/scanner.py +223 -0
- apsimo/skills_memory/__init__.py +26 -0
- apsimo/skills_memory/distill.py +159 -0
- apsimo/skills_memory/models.py +85 -0
- apsimo/skills_memory/retrieve.py +62 -0
- apsimo/skills_memory/store.py +172 -0
- apsimo/surprise/__init__.py +6 -0
- apsimo/surprise/accumulation.py +57 -0
- apsimo/surprise/scorer.py +102 -0
- apsimo/surprise/store.py +203 -0
- apsimo/task_queue/__init__.py +69 -0
- apsimo/task_queue/action_receipts.py +148 -0
- apsimo/task_queue/approval_relay_canary.py +108 -0
- apsimo/task_queue/config.py +85 -0
- apsimo/task_queue/contract.py +361 -0
- apsimo/task_queue/events.py +130 -0
- apsimo/task_queue/governor.py +1031 -0
- apsimo/task_queue/handlers/__init__.py +16 -0
- apsimo/task_queue/handlers/base.py +37 -0
- apsimo/task_queue/handlers/inference.py +640 -0
- apsimo/task_queue/handlers/monitoring.py +116 -0
- apsimo/task_queue/handlers/registry.py +75 -0
- apsimo/task_queue/handlers/subtask_handler.py +173 -0
- apsimo/task_queue/handlers/system_maintenance.py +147 -0
- apsimo/task_queue/mesh_integration.py +111 -0
- apsimo/task_queue/models.py +317 -0
- apsimo/task_queue/queue_manager.py +8286 -0
- apsimo/task_queue/routing.py +287 -0
- apsimo/task_queue/scheduler.py +252 -0
- apsimo/task_queue/schema.sql +197 -0
- apsimo/task_queue/work_control.py +342 -0
- apsimo/task_queue/worker.py +993 -0
- apsimo/telemetry.py +145 -0
- apsimo/tom/__init__.py +6 -0
- apsimo/tom/affect.py +387 -0
- apsimo/tom/approvals.py +171 -0
- apsimo/tom/arcs.py +896 -0
- apsimo/tom/asymmetry.py +131 -0
- apsimo/tom/eligibility.py +248 -0
- apsimo/tom/engagement.py +214 -0
- apsimo/tom/exposure.py +214 -0
- apsimo/tom/extractor.py +306 -0
- apsimo/tom/fact_adapters.py +144 -0
- apsimo/tom/facts.py +326 -0
- apsimo/tom/integration.py +592 -0
- apsimo/tom/leveled.py +118 -0
- apsimo/tom/levels.py +247 -0
- apsimo/tom/recipient_audit.py +995 -0
- apsimo/tom/recipient_simulator.py +593 -0
- apsimo/tom/source_lineage.py +93 -0
- apsimo/tom/tom2.py +277 -0
- apsimo/tom/visibility.py +559 -0
- apsimo/tom/visibility_store.py +414 -0
- apsimo/tools/__init__.py +0 -0
- apsimo/tools/definitions.py +740 -0
- apsimo/tools/handlers.py +943 -0
- apsimo/toolsmith/__init__.py +26 -0
- apsimo/toolsmith/authority.py +166 -0
- apsimo/toolsmith/engine.py +559 -0
- apsimo/toolsmith/integrity.py +100 -0
- apsimo/toolsmith/miner.py +145 -0
- apsimo/toolsmith/policy.py +110 -0
- apsimo/toolsmith/registry.py +635 -0
- apsimo/turns/__init__.py +17 -0
- apsimo/turns/audio.py +134 -0
- apsimo/turns/documents.py +235 -0
- apsimo/turns/executions.py +486 -0
- apsimo/turns/hermes_history.py +245 -0
- apsimo/turns/hermes_kanban.py +268 -0
- apsimo/turns/hermes_work.py +96 -0
- apsimo/turns/idempotency.py +752 -0
- apsimo/turns/local_work.py +115 -0
- apsimo/turns/media.py +581 -0
- apsimo/turns/reported_workers.py +196 -0
- apsimo/turns/source_annotations.py +283 -0
- apsimo/turns/source_attribution.py +154 -0
- apsimo/turns/source_read.py +351 -0
- apsimo/turns/source_vectors.py +263 -0
- apsimo/turns/video.py +210 -0
- apsimo/util/autonomy_preset.py +220 -0
- apsimo/util/instance.py +92 -0
- apsimo/util/model_output.py +25 -0
- apsimo/util/quiet_hours.py +27 -0
- apsimo/util/session_safety.py +37 -0
- apsimo/util/temporal.py +343 -0
- apsimo/vector/__init__.py +75 -0
- apsimo/vector/backfill.py +171 -0
- apsimo/vector/caption.py +114 -0
- apsimo/vector/collections.py +51 -0
- apsimo/vector/config.py +102 -0
- apsimo/vector/embedder.py +670 -0
- apsimo/vector/image_preprocess.py +406 -0
- apsimo/vector/image_store.py +296 -0
- apsimo/vector/indexes.py +162 -0
- apsimo/vector/migrate.py +334 -0
- apsimo/vector/multimodal_provider.py +417 -0
- apsimo/vector/multimodal_types.py +87 -0
- apsimo/vector/openai_provider.py +119 -0
- apsimo/vector/query.py +49 -0
- apsimo/vector/reranker.py +565 -0
- apsimo/vector/safety_image.py +159 -0
- apsimo/vector/scanner.py +197 -0
- apsimo/vector/setup.py +289 -0
- apsimo/vector/store.py +533 -0
- apsimo/vector/tiers.py +263 -0
- apsimo/work_orders.py +925 -0
- apsimo/workers/__init__.py +21 -0
- apsimo/workers/agent_bridge.py +640 -0
- apsimo/workers/colony_worker.py +382 -0
- apsimo/workers/queue_worker.py +441 -0
- apsimo/workers/skills_sync.py +152 -0
- apsimo/world_model/__init__.py +71 -0
- apsimo/world_model/causal_maintenance.py +131 -0
- apsimo/world_model/causal_policy.py +43 -0
- apsimo/world_model/causal_query.py +125 -0
- apsimo/world_model/confidence.py +54 -0
- apsimo/world_model/config.py +64 -0
- apsimo/world_model/constants.py +97 -0
- apsimo/world_model/entities.py +145 -0
- apsimo/world_model/expectation_resolvers.py +177 -0
- apsimo/world_model/extraction/__init__.py +7 -0
- apsimo/world_model/extraction/base.py +62 -0
- apsimo/world_model/extraction/conversation_extractor.py +262 -0
- apsimo/world_model/extraction/detector.py +74 -0
- apsimo/world_model/extraction/document_extractor.py +78 -0
- apsimo/world_model/extraction/formats/__init__.py +24 -0
- apsimo/world_model/extraction/formats/csv_fmt.py +68 -0
- apsimo/world_model/extraction/formats/html_fmt.py +72 -0
- apsimo/world_model/extraction/formats/json_fmt.py +68 -0
- apsimo/world_model/extraction/formats/pdf.py +43 -0
- apsimo/world_model/extraction/formats/text.py +27 -0
- apsimo/world_model/extraction/llm_extractor.py +164 -0
- apsimo/world_model/extraction/pipeline.py +73 -0
- apsimo/world_model/integrations/__init__.py +5 -0
- apsimo/world_model/integrations/mind_model_bridge.py +115 -0
- apsimo/world_model/integrations/social_intel_bridge.py +120 -0
- apsimo/world_model/jobs/__init__.py +4 -0
- apsimo/world_model/jobs/extraction_job.py +168 -0
- apsimo/world_model/llm_extract.py +572 -0
- apsimo/world_model/neo4j/__init__.py +5 -0
- apsimo/world_model/neo4j/backend.py +654 -0
- apsimo/world_model/observations.py +155 -0
- apsimo/world_model/populator.py +307 -0
- apsimo/world_model/postgres/__init__.py +1 -0
- apsimo/world_model/postgres/backend.py +683 -0
- apsimo/world_model/relationships.py +25 -0
- apsimo/world_model/resolution/__init__.py +13 -0
- apsimo/world_model/resolution/entity_resolver.py +232 -0
- apsimo/world_model/resolution/merge_audit.py +16 -0
- apsimo/world_model/resolution/merge_workflow.py +117 -0
- apsimo/world_model/source_reports.py +121 -0
- apsimo/world_model/sqlite/__init__.py +4 -0
- apsimo/world_model/sqlite/backend.py +855 -0
- apsimo/world_model/sqlite/schema.sql +132 -0
- apsimo/world_model/store.py +545 -0
- apsimo-1.3.0.dist-info/METADATA +78 -0
- apsimo-1.3.0.dist-info/RECORD +614 -0
- apsimo-1.3.0.dist-info/WHEEL +5 -0
- apsimo-1.3.0.dist-info/entry_points.txt +11 -0
- apsimo-1.3.0.dist-info/licenses/LICENSE +21 -0
- apsimo-1.3.0.dist-info/top_level.txt +2 -0
- colony_sidecar/__init__.py +4 -0
|
@@ -0,0 +1,3530 @@
|
|
|
1
|
+
"""P7 owner-visible drives and immutable charter governance.
|
|
2
|
+
|
|
3
|
+
This module is a side-effect-free priority layer around P3's goal spine. It
|
|
4
|
+
does not create projects, execute actions, or mint authority. A ranking is
|
|
5
|
+
considered eligible only after the server resolves all five persisted P3
|
|
6
|
+
policy decisions, and every result explicitly declares that it has no
|
|
7
|
+
authorization effect.
|
|
8
|
+
|
|
9
|
+
Charter changes reuse Colony's transport-neutral ``ApprovalAuthorityStore``.
|
|
10
|
+
Models may propose immutable revisions, but activation and revocation require
|
|
11
|
+
an exact, unexpired approval request plus the server-derived scoped owner
|
|
12
|
+
``RequestAuthority``. Lifecycle state is append-only and every proposal and
|
|
13
|
+
ratification operation is replay fenced.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from dataclasses import asdict, dataclass
|
|
19
|
+
from datetime import datetime, timedelta, timezone
|
|
20
|
+
import hashlib
|
|
21
|
+
import json
|
|
22
|
+
import os
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
import re
|
|
25
|
+
import sqlite3
|
|
26
|
+
import threading
|
|
27
|
+
from typing import Any, Callable, Dict, Iterable, Mapping, Optional, Sequence
|
|
28
|
+
|
|
29
|
+
from apsimo.initiatives.approval_authority import (
|
|
30
|
+
ActionBinding,
|
|
31
|
+
ApprovalAuthorityStore,
|
|
32
|
+
ApprovalSubjectBinding,
|
|
33
|
+
PRESENTATION_SCHEMA,
|
|
34
|
+
)
|
|
35
|
+
from apsimo.scope_bounds import VIEWER_SCOPE_MAX_CHARS
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
DRIVE_GOVERNANCE_SCHEMA_VERSION = 7
|
|
39
|
+
_MODES = frozenset({"off", "shadow", "bootstrap", "live"})
|
|
40
|
+
_SHAREABILITY = frozenset({
|
|
41
|
+
"owner_private", "subject_private", "shared", "public",
|
|
42
|
+
})
|
|
43
|
+
_DRIVE_STATES = frozenset({"enabled", "disabled"})
|
|
44
|
+
_SIGNAL_STATES = frozenset({"active", "disabled", "unknown"})
|
|
45
|
+
_TRANSITIONS = frozenset({"activate", "revoke"})
|
|
46
|
+
CHARTER_TRANSITION_APPROVAL_PROJECTION_SCHEMA = (
|
|
47
|
+
"ColonyCharterTransitionApprovalProjectionV1"
|
|
48
|
+
)
|
|
49
|
+
_REQUIRED_P3_STAGES = (
|
|
50
|
+
"charter", "boundary", "situation", "duplicate", "authority",
|
|
51
|
+
)
|
|
52
|
+
_SAFE_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:@/+\-]{0,255}$")
|
|
53
|
+
_SAFE_KEY = re.compile(r"^[a-z][a-z0-9_.-]{0,63}$")
|
|
54
|
+
_SECRET_ASSIGNMENT = re.compile(
|
|
55
|
+
r"(?i)\b(api[_-]?key|authorization|bearer|credential|password|secret|token)"
|
|
56
|
+
r"\s*[:=]\s*(?:bearer\s+)?[^\s,;]+"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class DriveGovernanceError(ValueError):
|
|
61
|
+
"""Stable, public-safe governance failure."""
|
|
62
|
+
|
|
63
|
+
def __init__(self, code: str, message: str) -> None:
|
|
64
|
+
super().__init__(message)
|
|
65
|
+
self.code = code
|
|
66
|
+
self.message = message
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def drive_governance_mode() -> str:
|
|
70
|
+
"""Return the explicit migration mode; unset/invalid always fails off."""
|
|
71
|
+
|
|
72
|
+
value = os.environ.get("COLONY_DRIVE_GOVERNANCE_MODE", "off").strip().lower()
|
|
73
|
+
return value if value in _MODES else "off"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _normalized_mode(value: Optional[str]) -> str:
|
|
77
|
+
if value is None:
|
|
78
|
+
return drive_governance_mode()
|
|
79
|
+
normalized = str(value).strip().lower()
|
|
80
|
+
return normalized if normalized in _MODES else "off"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _canonical(value: Any) -> str:
|
|
84
|
+
def default(item: Any) -> str:
|
|
85
|
+
if isinstance(item, datetime):
|
|
86
|
+
return _iso(item)
|
|
87
|
+
return str(item)
|
|
88
|
+
|
|
89
|
+
return json.dumps(
|
|
90
|
+
value,
|
|
91
|
+
sort_keys=True,
|
|
92
|
+
separators=(",", ":"),
|
|
93
|
+
ensure_ascii=False,
|
|
94
|
+
default=default,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _digest(value: Any) -> str:
|
|
99
|
+
return hashlib.sha256(_canonical(value).encode("utf-8")).hexdigest()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _as_utc(value: Optional[datetime]) -> datetime:
|
|
103
|
+
result = value or datetime.now(timezone.utc)
|
|
104
|
+
if result.tzinfo is None:
|
|
105
|
+
result = result.replace(tzinfo=timezone.utc)
|
|
106
|
+
return result.astimezone(timezone.utc)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _iso(value: datetime) -> str:
|
|
110
|
+
return _as_utc(value).isoformat()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _parse_time(value: Any, *, field: str) -> datetime:
|
|
114
|
+
if isinstance(value, datetime):
|
|
115
|
+
return _as_utc(value)
|
|
116
|
+
text = str(value or "").strip()
|
|
117
|
+
if text.endswith("Z"):
|
|
118
|
+
text = text[:-1] + "+00:00"
|
|
119
|
+
try:
|
|
120
|
+
parsed = datetime.fromisoformat(text)
|
|
121
|
+
except (TypeError, ValueError) as exc:
|
|
122
|
+
raise DriveGovernanceError(
|
|
123
|
+
"invalid_time", f"{field} must be an RFC3339 timestamp",
|
|
124
|
+
) from exc
|
|
125
|
+
if parsed.tzinfo is None:
|
|
126
|
+
raise DriveGovernanceError(
|
|
127
|
+
"invalid_time", f"{field} must include a timezone",
|
|
128
|
+
)
|
|
129
|
+
return parsed.astimezone(timezone.utc)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _text(value: Any, field: str, maximum: int, *, required: bool = True) -> str:
|
|
133
|
+
result = " ".join(str(value or "").split()).strip()
|
|
134
|
+
if required and not result:
|
|
135
|
+
raise DriveGovernanceError("invalid_text", f"{field} is required")
|
|
136
|
+
if len(result) > maximum:
|
|
137
|
+
raise DriveGovernanceError(
|
|
138
|
+
"invalid_text", f"{field} exceeds {maximum} characters",
|
|
139
|
+
)
|
|
140
|
+
return _SECRET_ASSIGNMENT.sub(
|
|
141
|
+
lambda match: f"{match.group(1)}=[REDACTED]", result,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _identifier(value: Any, field: str, *, maximum: int = 256) -> str:
|
|
146
|
+
result = str(value or "").strip()
|
|
147
|
+
if len(result) > maximum or not _SAFE_ID.fullmatch(result):
|
|
148
|
+
raise DriveGovernanceError("invalid_identifier", f"{field} is invalid")
|
|
149
|
+
return result
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _operation_id(value: Any) -> str:
|
|
153
|
+
result = _identifier(value, "operation_id")
|
|
154
|
+
if len(result) < 8:
|
|
155
|
+
raise DriveGovernanceError(
|
|
156
|
+
"invalid_operation_id", "operation_id must be at least 8 characters",
|
|
157
|
+
)
|
|
158
|
+
return result
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _refs(
|
|
162
|
+
values: Iterable[Any],
|
|
163
|
+
*,
|
|
164
|
+
field: str = "evidence_refs",
|
|
165
|
+
maximum: int = 40,
|
|
166
|
+
required: bool = False,
|
|
167
|
+
) -> tuple[str, ...]:
|
|
168
|
+
result: list[str] = []
|
|
169
|
+
for raw in values or ():
|
|
170
|
+
value = str(raw or "").strip()
|
|
171
|
+
if not _SAFE_ID.fullmatch(value):
|
|
172
|
+
raise DriveGovernanceError(
|
|
173
|
+
"invalid_evidence", f"{field} contains an invalid reference",
|
|
174
|
+
)
|
|
175
|
+
if value not in result:
|
|
176
|
+
result.append(value)
|
|
177
|
+
if len(result) > maximum:
|
|
178
|
+
raise DriveGovernanceError(
|
|
179
|
+
"evidence_budget_exceeded",
|
|
180
|
+
f"{field} exceeds {maximum} references",
|
|
181
|
+
)
|
|
182
|
+
if required and not result:
|
|
183
|
+
raise DriveGovernanceError(
|
|
184
|
+
"evidence_required", f"{field} requires evidence",
|
|
185
|
+
)
|
|
186
|
+
return tuple(result)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _bounded_float(
|
|
190
|
+
value: Any,
|
|
191
|
+
field: str,
|
|
192
|
+
*,
|
|
193
|
+
minimum: float,
|
|
194
|
+
maximum: float,
|
|
195
|
+
) -> float:
|
|
196
|
+
if isinstance(value, bool):
|
|
197
|
+
raise DriveGovernanceError("invalid_number", f"{field} must be numeric")
|
|
198
|
+
try:
|
|
199
|
+
result = float(value)
|
|
200
|
+
except (TypeError, ValueError) as exc:
|
|
201
|
+
raise DriveGovernanceError(
|
|
202
|
+
"invalid_number", f"{field} must be numeric",
|
|
203
|
+
) from exc
|
|
204
|
+
if not minimum <= result <= maximum:
|
|
205
|
+
raise DriveGovernanceError(
|
|
206
|
+
"invalid_number", f"{field} must be between {minimum} and {maximum}",
|
|
207
|
+
)
|
|
208
|
+
return round(result, 8)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _bounded_int(value: Any, field: str, *, minimum: int, maximum: int) -> int:
|
|
212
|
+
if isinstance(value, bool):
|
|
213
|
+
raise DriveGovernanceError("invalid_number", f"{field} must be an integer")
|
|
214
|
+
try:
|
|
215
|
+
result = int(value)
|
|
216
|
+
except (TypeError, ValueError) as exc:
|
|
217
|
+
raise DriveGovernanceError(
|
|
218
|
+
"invalid_number", f"{field} must be an integer",
|
|
219
|
+
) from exc
|
|
220
|
+
if result != value and not isinstance(value, str):
|
|
221
|
+
raise DriveGovernanceError("invalid_number", f"{field} must be an integer")
|
|
222
|
+
if not minimum <= result <= maximum:
|
|
223
|
+
raise DriveGovernanceError(
|
|
224
|
+
"invalid_number", f"{field} must be between {minimum} and {maximum}",
|
|
225
|
+
)
|
|
226
|
+
return result
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@dataclass(frozen=True)
|
|
230
|
+
class ScopeV1:
|
|
231
|
+
"""Scope carried from evidence through signals and ranking projections."""
|
|
232
|
+
|
|
233
|
+
subject_person_id: str
|
|
234
|
+
viewer_scope: str
|
|
235
|
+
shareability: str
|
|
236
|
+
schema: str = "ScopeV1"
|
|
237
|
+
version: int = 1
|
|
238
|
+
|
|
239
|
+
def __post_init__(self) -> None:
|
|
240
|
+
_identifier(self.subject_person_id, "subject_person_id", maximum=128)
|
|
241
|
+
_identifier(
|
|
242
|
+
self.viewer_scope, "viewer_scope",
|
|
243
|
+
maximum=VIEWER_SCOPE_MAX_CHARS,
|
|
244
|
+
)
|
|
245
|
+
if self.shareability not in _SHAREABILITY:
|
|
246
|
+
raise DriveGovernanceError(
|
|
247
|
+
"invalid_scope", "shareability is not recognized",
|
|
248
|
+
)
|
|
249
|
+
expected = {
|
|
250
|
+
"owner_private": "owner",
|
|
251
|
+
"subject_private": f"person:{self.subject_person_id}",
|
|
252
|
+
"shared": "shared",
|
|
253
|
+
"public": "public",
|
|
254
|
+
}[self.shareability]
|
|
255
|
+
if self.viewer_scope != expected:
|
|
256
|
+
raise DriveGovernanceError(
|
|
257
|
+
"invalid_scope",
|
|
258
|
+
"viewer_scope must match the declared shareability lane",
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
def payload(self) -> Dict[str, Any]:
|
|
262
|
+
return asdict(self)
|
|
263
|
+
|
|
264
|
+
def visible_to(
|
|
265
|
+
self,
|
|
266
|
+
*,
|
|
267
|
+
viewer_person_id: str,
|
|
268
|
+
owner_person_id: str,
|
|
269
|
+
audiences: set[str] | frozenset[str] = frozenset(),
|
|
270
|
+
) -> bool:
|
|
271
|
+
viewer = str(viewer_person_id or "").strip()
|
|
272
|
+
owner = str(owner_person_id or "").strip()
|
|
273
|
+
if not viewer:
|
|
274
|
+
return False
|
|
275
|
+
if owner and viewer == owner:
|
|
276
|
+
return True
|
|
277
|
+
if self.shareability == "public":
|
|
278
|
+
return "global" in audiences
|
|
279
|
+
if self.shareability == "shared":
|
|
280
|
+
return "shared" in audiences
|
|
281
|
+
if self.shareability == "subject_private":
|
|
282
|
+
return viewer == self.subject_person_id
|
|
283
|
+
return False
|
|
284
|
+
|
|
285
|
+
def permits_child(self, child: "ScopeV1") -> bool:
|
|
286
|
+
"""Return whether ``child`` is no broader than this scope."""
|
|
287
|
+
|
|
288
|
+
if self.shareability == "public":
|
|
289
|
+
return True
|
|
290
|
+
if self.shareability == "shared":
|
|
291
|
+
return child.shareability in {
|
|
292
|
+
"shared", "owner_private",
|
|
293
|
+
}
|
|
294
|
+
if self.shareability == "owner_private":
|
|
295
|
+
return child.shareability == "owner_private"
|
|
296
|
+
return (
|
|
297
|
+
child.shareability == "owner_private"
|
|
298
|
+
or (
|
|
299
|
+
child.shareability == "subject_private"
|
|
300
|
+
and child.subject_person_id == self.subject_person_id
|
|
301
|
+
)
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _narrower_scope(first: ScopeV1, second: ScopeV1) -> ScopeV1:
|
|
306
|
+
if first.permits_child(second):
|
|
307
|
+
return second
|
|
308
|
+
if second.permits_child(first):
|
|
309
|
+
return first
|
|
310
|
+
# Incomparable private lanes can only be combined into an owner-visible
|
|
311
|
+
# projection; this prevents a score from leaking that a private signal
|
|
312
|
+
# exists to either subject.
|
|
313
|
+
subject = first.subject_person_id or second.subject_person_id
|
|
314
|
+
return ScopeV1(subject, "owner", "owner_private")
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@dataclass(frozen=True)
|
|
318
|
+
class RankingBudgetV1:
|
|
319
|
+
max_goals: int = 50
|
|
320
|
+
max_signals_per_drive: int = 5
|
|
321
|
+
max_total_signals: int = 250
|
|
322
|
+
max_evidence_refs_per_goal: int = 20
|
|
323
|
+
schema: str = "RankingBudgetV1"
|
|
324
|
+
version: int = 1
|
|
325
|
+
|
|
326
|
+
def __post_init__(self) -> None:
|
|
327
|
+
object.__setattr__(
|
|
328
|
+
self, "max_goals",
|
|
329
|
+
_bounded_int(self.max_goals, "max_goals", minimum=1, maximum=200),
|
|
330
|
+
)
|
|
331
|
+
object.__setattr__(
|
|
332
|
+
self, "max_signals_per_drive",
|
|
333
|
+
_bounded_int(
|
|
334
|
+
self.max_signals_per_drive,
|
|
335
|
+
"max_signals_per_drive", minimum=1, maximum=20,
|
|
336
|
+
),
|
|
337
|
+
)
|
|
338
|
+
object.__setattr__(
|
|
339
|
+
self, "max_total_signals",
|
|
340
|
+
_bounded_int(
|
|
341
|
+
self.max_total_signals,
|
|
342
|
+
"max_total_signals", minimum=1, maximum=2000,
|
|
343
|
+
),
|
|
344
|
+
)
|
|
345
|
+
object.__setattr__(
|
|
346
|
+
self, "max_evidence_refs_per_goal",
|
|
347
|
+
_bounded_int(
|
|
348
|
+
self.max_evidence_refs_per_goal,
|
|
349
|
+
"max_evidence_refs_per_goal", minimum=1, maximum=100,
|
|
350
|
+
),
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
def payload(self) -> Dict[str, Any]:
|
|
354
|
+
return asdict(self)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
@dataclass(frozen=True)
|
|
358
|
+
class DriveV1:
|
|
359
|
+
drive_id: str
|
|
360
|
+
definition_digest: str
|
|
361
|
+
key: str
|
|
362
|
+
version_label: str
|
|
363
|
+
title: str
|
|
364
|
+
definition_summary: str
|
|
365
|
+
max_abs_contribution: float
|
|
366
|
+
max_signals_per_goal: int
|
|
367
|
+
state: str
|
|
368
|
+
scope: ScopeV1
|
|
369
|
+
evidence_refs: tuple[str, ...]
|
|
370
|
+
created_at: str
|
|
371
|
+
expires_at: Optional[str]
|
|
372
|
+
schema: str = "DriveV1"
|
|
373
|
+
version: int = 1
|
|
374
|
+
|
|
375
|
+
@classmethod
|
|
376
|
+
def create(
|
|
377
|
+
cls,
|
|
378
|
+
*,
|
|
379
|
+
key: str,
|
|
380
|
+
version: str,
|
|
381
|
+
title: str,
|
|
382
|
+
definition_summary: str,
|
|
383
|
+
max_abs_contribution: float,
|
|
384
|
+
max_signals_per_goal: int,
|
|
385
|
+
state: str,
|
|
386
|
+
scope: ScopeV1,
|
|
387
|
+
evidence_refs: Iterable[str],
|
|
388
|
+
created_at: Optional[datetime] = None,
|
|
389
|
+
expires_at: Optional[datetime] = None,
|
|
390
|
+
) -> "DriveV1":
|
|
391
|
+
key = str(key or "").strip().lower()
|
|
392
|
+
if not _SAFE_KEY.fullmatch(key):
|
|
393
|
+
raise DriveGovernanceError("invalid_drive_key", "drive key is invalid")
|
|
394
|
+
version_label = _identifier(version, "drive version", maximum=32)
|
|
395
|
+
normalized_state = str(state or "").strip().lower()
|
|
396
|
+
if normalized_state not in _DRIVE_STATES:
|
|
397
|
+
raise DriveGovernanceError("invalid_drive_state", "drive state is invalid")
|
|
398
|
+
created = _as_utc(created_at)
|
|
399
|
+
expiry = _as_utc(expires_at) if expires_at is not None else None
|
|
400
|
+
if expiry is not None and expiry <= created:
|
|
401
|
+
raise DriveGovernanceError(
|
|
402
|
+
"invalid_expiry", "drive expiry must follow creation",
|
|
403
|
+
)
|
|
404
|
+
authority = {
|
|
405
|
+
"schema": "DriveV1", "version": 1,
|
|
406
|
+
"key": key,
|
|
407
|
+
"version_label": version_label,
|
|
408
|
+
"title": _text(title, "title", 120),
|
|
409
|
+
"definition_summary": _text(
|
|
410
|
+
definition_summary, "definition_summary", 600,
|
|
411
|
+
),
|
|
412
|
+
"max_abs_contribution": _bounded_float(
|
|
413
|
+
max_abs_contribution, "max_abs_contribution",
|
|
414
|
+
minimum=0.0, maximum=1.0,
|
|
415
|
+
),
|
|
416
|
+
"max_signals_per_goal": _bounded_int(
|
|
417
|
+
max_signals_per_goal, "max_signals_per_goal",
|
|
418
|
+
minimum=1, maximum=20,
|
|
419
|
+
),
|
|
420
|
+
"state": normalized_state,
|
|
421
|
+
"scope": scope.payload(),
|
|
422
|
+
"evidence_refs": list(_refs(
|
|
423
|
+
evidence_refs, required=True, maximum=20,
|
|
424
|
+
)),
|
|
425
|
+
"created_at": _iso(created),
|
|
426
|
+
"expires_at": _iso(expiry) if expiry else None,
|
|
427
|
+
}
|
|
428
|
+
digest = _digest(authority)
|
|
429
|
+
return cls(
|
|
430
|
+
drive_id=f"drive:{key}:{digest[:20]}",
|
|
431
|
+
definition_digest=digest,
|
|
432
|
+
key=key,
|
|
433
|
+
version_label=version_label,
|
|
434
|
+
title=authority["title"],
|
|
435
|
+
definition_summary=authority["definition_summary"],
|
|
436
|
+
max_abs_contribution=authority["max_abs_contribution"],
|
|
437
|
+
max_signals_per_goal=authority["max_signals_per_goal"],
|
|
438
|
+
state=normalized_state,
|
|
439
|
+
scope=scope,
|
|
440
|
+
evidence_refs=tuple(authority["evidence_refs"]),
|
|
441
|
+
created_at=authority["created_at"],
|
|
442
|
+
expires_at=authority["expires_at"],
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
def authority_payload(self) -> Dict[str, Any]:
|
|
446
|
+
return {
|
|
447
|
+
"schema": self.schema, "version": self.version,
|
|
448
|
+
"key": self.key,
|
|
449
|
+
"version_label": self.version_label,
|
|
450
|
+
"title": self.title,
|
|
451
|
+
"definition_summary": self.definition_summary,
|
|
452
|
+
"max_abs_contribution": self.max_abs_contribution,
|
|
453
|
+
"max_signals_per_goal": self.max_signals_per_goal,
|
|
454
|
+
"state": self.state,
|
|
455
|
+
"scope": self.scope.payload(),
|
|
456
|
+
"evidence_refs": list(self.evidence_refs),
|
|
457
|
+
"created_at": self.created_at,
|
|
458
|
+
"expires_at": self.expires_at,
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
def payload(self) -> Dict[str, Any]:
|
|
462
|
+
return {
|
|
463
|
+
**self.authority_payload(),
|
|
464
|
+
"drive_id": self.drive_id,
|
|
465
|
+
"definition_digest": self.definition_digest,
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
def validate_integrity(self) -> None:
|
|
469
|
+
if self.schema != "DriveV1" or self.version != 1:
|
|
470
|
+
raise DriveGovernanceError(
|
|
471
|
+
"unsupported_schema", "drive definition schema is unsupported",
|
|
472
|
+
)
|
|
473
|
+
if not _SAFE_KEY.fullmatch(str(self.key or "")):
|
|
474
|
+
raise DriveGovernanceError("invalid_drive_key", "drive key is invalid")
|
|
475
|
+
_identifier(self.version_label, "drive version", maximum=32)
|
|
476
|
+
if _text(self.title, "title", 120) != self.title or _text(
|
|
477
|
+
self.definition_summary, "definition_summary", 600,
|
|
478
|
+
) != self.definition_summary:
|
|
479
|
+
raise DriveGovernanceError(
|
|
480
|
+
"invalid_text", "drive text is not normalized and bounded",
|
|
481
|
+
)
|
|
482
|
+
if self.state not in _DRIVE_STATES:
|
|
483
|
+
raise DriveGovernanceError("invalid_drive_state", "drive state is invalid")
|
|
484
|
+
_bounded_float(
|
|
485
|
+
self.max_abs_contribution, "max_abs_contribution",
|
|
486
|
+
minimum=0.0, maximum=1.0,
|
|
487
|
+
)
|
|
488
|
+
_bounded_int(
|
|
489
|
+
self.max_signals_per_goal, "max_signals_per_goal",
|
|
490
|
+
minimum=1, maximum=20,
|
|
491
|
+
)
|
|
492
|
+
if _refs(
|
|
493
|
+
self.evidence_refs, required=True, maximum=20,
|
|
494
|
+
) != self.evidence_refs:
|
|
495
|
+
raise DriveGovernanceError(
|
|
496
|
+
"invalid_evidence", "drive evidence references are not canonical",
|
|
497
|
+
)
|
|
498
|
+
created = _parse_time(self.created_at, field="drive created_at")
|
|
499
|
+
if self.expires_at and _parse_time(
|
|
500
|
+
self.expires_at, field="drive expires_at",
|
|
501
|
+
) <= created:
|
|
502
|
+
raise DriveGovernanceError(
|
|
503
|
+
"invalid_expiry", "drive expiry must follow creation",
|
|
504
|
+
)
|
|
505
|
+
digest = _digest(self.authority_payload())
|
|
506
|
+
if digest != self.definition_digest\
|
|
507
|
+
or self.drive_id != f"drive:{self.key}:{digest[:20]}":
|
|
508
|
+
raise DriveGovernanceError(
|
|
509
|
+
"immutable_object_tampered", "drive definition integrity mismatch",
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
def state_at(self, now: Optional[datetime] = None) -> str:
|
|
513
|
+
if self.state == "disabled":
|
|
514
|
+
return "disabled"
|
|
515
|
+
if self.expires_at and _parse_time(
|
|
516
|
+
self.expires_at, field="drive expires_at",
|
|
517
|
+
) <= _as_utc(now):
|
|
518
|
+
return "expired"
|
|
519
|
+
return "enabled"
|
|
520
|
+
|
|
521
|
+
@classmethod
|
|
522
|
+
def from_payload(cls, payload: Mapping[str, Any]) -> "DriveV1":
|
|
523
|
+
result = cls(
|
|
524
|
+
drive_id=str(payload["drive_id"]),
|
|
525
|
+
definition_digest=str(payload["definition_digest"]),
|
|
526
|
+
key=str(payload["key"]),
|
|
527
|
+
version_label=str(payload["version_label"]),
|
|
528
|
+
title=str(payload["title"]),
|
|
529
|
+
definition_summary=str(payload["definition_summary"]),
|
|
530
|
+
max_abs_contribution=float(payload["max_abs_contribution"]),
|
|
531
|
+
max_signals_per_goal=int(payload["max_signals_per_goal"]),
|
|
532
|
+
state=str(payload["state"]),
|
|
533
|
+
scope=ScopeV1(**dict(payload["scope"])),
|
|
534
|
+
evidence_refs=tuple(payload.get("evidence_refs") or ()),
|
|
535
|
+
created_at=str(payload["created_at"]),
|
|
536
|
+
expires_at=(
|
|
537
|
+
str(payload["expires_at"]) if payload.get("expires_at") else None
|
|
538
|
+
),
|
|
539
|
+
)
|
|
540
|
+
result.validate_integrity()
|
|
541
|
+
return result
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
@dataclass(frozen=True)
|
|
545
|
+
class DriveSignalV1:
|
|
546
|
+
signal_id: str
|
|
547
|
+
signal_digest: str
|
|
548
|
+
drive_id: str
|
|
549
|
+
goal_fingerprint: str
|
|
550
|
+
normalized_value: float
|
|
551
|
+
confidence: float
|
|
552
|
+
state: str
|
|
553
|
+
rationale_summary: str
|
|
554
|
+
evidence_refs: tuple[str, ...]
|
|
555
|
+
observed_at: str
|
|
556
|
+
expires_at: str
|
|
557
|
+
scope: ScopeV1
|
|
558
|
+
schema: str = "DriveSignalV1"
|
|
559
|
+
version: int = 1
|
|
560
|
+
|
|
561
|
+
@classmethod
|
|
562
|
+
def derive(
|
|
563
|
+
cls,
|
|
564
|
+
*,
|
|
565
|
+
drive: DriveV1,
|
|
566
|
+
goal_fingerprint: str,
|
|
567
|
+
normalized_value: float,
|
|
568
|
+
confidence: float,
|
|
569
|
+
rationale_summary: str,
|
|
570
|
+
evidence_refs: Iterable[str],
|
|
571
|
+
observed_at: datetime,
|
|
572
|
+
expires_at: datetime,
|
|
573
|
+
scope: ScopeV1,
|
|
574
|
+
state: str = "active",
|
|
575
|
+
) -> "DriveSignalV1":
|
|
576
|
+
drive.validate_integrity()
|
|
577
|
+
fingerprint = _identifier(
|
|
578
|
+
goal_fingerprint, "goal_fingerprint", maximum=192,
|
|
579
|
+
)
|
|
580
|
+
normalized_state = str(state or "").strip().lower()
|
|
581
|
+
if normalized_state not in _SIGNAL_STATES:
|
|
582
|
+
raise DriveGovernanceError(
|
|
583
|
+
"invalid_signal_state", "signal state is invalid",
|
|
584
|
+
)
|
|
585
|
+
if not drive.scope.permits_child(scope):
|
|
586
|
+
raise DriveGovernanceError(
|
|
587
|
+
"scope_broadening", "signal scope cannot broaden its drive scope",
|
|
588
|
+
)
|
|
589
|
+
observed = _as_utc(observed_at)
|
|
590
|
+
expiry = _as_utc(expires_at)
|
|
591
|
+
if expiry <= observed:
|
|
592
|
+
raise DriveGovernanceError(
|
|
593
|
+
"invalid_expiry", "signal expiry must follow observation",
|
|
594
|
+
)
|
|
595
|
+
if expiry - observed > timedelta(days=90):
|
|
596
|
+
raise DriveGovernanceError(
|
|
597
|
+
"invalid_expiry", "signal lifetime cannot exceed 90 days",
|
|
598
|
+
)
|
|
599
|
+
value = _bounded_float(
|
|
600
|
+
normalized_value, "normalized_value", minimum=-1.0, maximum=1.0,
|
|
601
|
+
)
|
|
602
|
+
if normalized_state != "active" and value != 0.0:
|
|
603
|
+
# Disabled/unknown evidence is explicit state, not a hidden score.
|
|
604
|
+
value = 0.0
|
|
605
|
+
authority = {
|
|
606
|
+
"schema": "DriveSignalV1", "version": 1,
|
|
607
|
+
"drive_id": drive.drive_id,
|
|
608
|
+
"goal_fingerprint": fingerprint,
|
|
609
|
+
"normalized_value": value,
|
|
610
|
+
"confidence": _bounded_float(
|
|
611
|
+
confidence, "confidence", minimum=0.0, maximum=1.0,
|
|
612
|
+
),
|
|
613
|
+
"state": normalized_state,
|
|
614
|
+
"rationale_summary": _text(
|
|
615
|
+
rationale_summary, "rationale_summary", 500,
|
|
616
|
+
),
|
|
617
|
+
"evidence_refs": list(_refs(
|
|
618
|
+
evidence_refs, required=True, maximum=20,
|
|
619
|
+
)),
|
|
620
|
+
"observed_at": _iso(observed),
|
|
621
|
+
"expires_at": _iso(expiry),
|
|
622
|
+
"scope": scope.payload(),
|
|
623
|
+
}
|
|
624
|
+
digest = _digest(authority)
|
|
625
|
+
return cls(
|
|
626
|
+
signal_id=f"drive-signal:{digest[:24]}",
|
|
627
|
+
signal_digest=digest,
|
|
628
|
+
drive_id=drive.drive_id,
|
|
629
|
+
goal_fingerprint=fingerprint,
|
|
630
|
+
normalized_value=value,
|
|
631
|
+
confidence=authority["confidence"],
|
|
632
|
+
state=normalized_state,
|
|
633
|
+
rationale_summary=authority["rationale_summary"],
|
|
634
|
+
evidence_refs=tuple(authority["evidence_refs"]),
|
|
635
|
+
observed_at=authority["observed_at"],
|
|
636
|
+
expires_at=authority["expires_at"],
|
|
637
|
+
scope=scope,
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
def authority_payload(self) -> Dict[str, Any]:
|
|
641
|
+
return {
|
|
642
|
+
"schema": self.schema, "version": self.version,
|
|
643
|
+
"drive_id": self.drive_id,
|
|
644
|
+
"goal_fingerprint": self.goal_fingerprint,
|
|
645
|
+
"normalized_value": self.normalized_value,
|
|
646
|
+
"confidence": self.confidence,
|
|
647
|
+
"state": self.state,
|
|
648
|
+
"rationale_summary": self.rationale_summary,
|
|
649
|
+
"evidence_refs": list(self.evidence_refs),
|
|
650
|
+
"observed_at": self.observed_at,
|
|
651
|
+
"expires_at": self.expires_at,
|
|
652
|
+
"scope": self.scope.payload(),
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
def payload(self) -> Dict[str, Any]:
|
|
656
|
+
return {
|
|
657
|
+
**self.authority_payload(),
|
|
658
|
+
"signal_id": self.signal_id,
|
|
659
|
+
"signal_digest": self.signal_digest,
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
def validate_integrity(self) -> None:
|
|
663
|
+
if self.schema != "DriveSignalV1" or self.version != 1:
|
|
664
|
+
raise DriveGovernanceError(
|
|
665
|
+
"unsupported_schema", "drive signal schema is unsupported",
|
|
666
|
+
)
|
|
667
|
+
_identifier(self.drive_id, "drive_id")
|
|
668
|
+
_identifier(self.goal_fingerprint, "goal_fingerprint", maximum=192)
|
|
669
|
+
if self.state not in _SIGNAL_STATES:
|
|
670
|
+
raise DriveGovernanceError(
|
|
671
|
+
"invalid_signal_state", "signal state is invalid",
|
|
672
|
+
)
|
|
673
|
+
value = _bounded_float(
|
|
674
|
+
self.normalized_value, "normalized_value",
|
|
675
|
+
minimum=-1.0, maximum=1.0,
|
|
676
|
+
)
|
|
677
|
+
_bounded_float(self.confidence, "confidence", minimum=0.0, maximum=1.0)
|
|
678
|
+
if self.state != "active" and value != 0.0:
|
|
679
|
+
raise DriveGovernanceError(
|
|
680
|
+
"invalid_signal_state",
|
|
681
|
+
"disabled or unknown signals cannot carry a score",
|
|
682
|
+
)
|
|
683
|
+
if _text(
|
|
684
|
+
self.rationale_summary, "rationale_summary", 500,
|
|
685
|
+
) != self.rationale_summary:
|
|
686
|
+
raise DriveGovernanceError(
|
|
687
|
+
"invalid_text", "signal rationale is not normalized and bounded",
|
|
688
|
+
)
|
|
689
|
+
if _refs(
|
|
690
|
+
self.evidence_refs, required=True, maximum=20,
|
|
691
|
+
) != self.evidence_refs:
|
|
692
|
+
raise DriveGovernanceError(
|
|
693
|
+
"invalid_evidence", "signal evidence references are not canonical",
|
|
694
|
+
)
|
|
695
|
+
observed = _parse_time(self.observed_at, field="signal observed_at")
|
|
696
|
+
expiry = _parse_time(self.expires_at, field="signal expires_at")
|
|
697
|
+
if expiry <= observed or expiry - observed > timedelta(days=90):
|
|
698
|
+
raise DriveGovernanceError(
|
|
699
|
+
"invalid_expiry", "signal expiry is outside its bounded lifetime",
|
|
700
|
+
)
|
|
701
|
+
digest = _digest(self.authority_payload())
|
|
702
|
+
if digest != self.signal_digest\
|
|
703
|
+
or self.signal_id != f"drive-signal:{digest[:24]}":
|
|
704
|
+
raise DriveGovernanceError(
|
|
705
|
+
"immutable_object_tampered", "drive signal integrity mismatch",
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
def state_at(self, now: Optional[datetime] = None) -> str:
|
|
709
|
+
if _parse_time(self.expires_at, field="signal expires_at") <= _as_utc(now):
|
|
710
|
+
return "expired"
|
|
711
|
+
return self.state
|
|
712
|
+
|
|
713
|
+
@classmethod
|
|
714
|
+
def from_payload(cls, payload: Mapping[str, Any]) -> "DriveSignalV1":
|
|
715
|
+
result = cls(
|
|
716
|
+
signal_id=str(payload["signal_id"]),
|
|
717
|
+
signal_digest=str(payload["signal_digest"]),
|
|
718
|
+
drive_id=str(payload["drive_id"]),
|
|
719
|
+
goal_fingerprint=str(payload["goal_fingerprint"]),
|
|
720
|
+
normalized_value=float(payload["normalized_value"]),
|
|
721
|
+
confidence=float(payload["confidence"]),
|
|
722
|
+
state=str(payload["state"]),
|
|
723
|
+
rationale_summary=str(payload["rationale_summary"]),
|
|
724
|
+
evidence_refs=tuple(payload.get("evidence_refs") or ()),
|
|
725
|
+
observed_at=str(payload["observed_at"]),
|
|
726
|
+
expires_at=str(payload["expires_at"]),
|
|
727
|
+
scope=ScopeV1(**dict(payload["scope"])),
|
|
728
|
+
)
|
|
729
|
+
result.validate_integrity()
|
|
730
|
+
return result
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
@dataclass(frozen=True)
|
|
734
|
+
class CharterAdmissionConstraintsV1:
|
|
735
|
+
"""Deterministic upper bounds for P3 admission under one charter."""
|
|
736
|
+
|
|
737
|
+
objective_allow_terms: tuple[str, ...] = ()
|
|
738
|
+
objective_deny_terms: tuple[str, ...] = (
|
|
739
|
+
"destroy", "drop", "format", "overwrite", "wipe",
|
|
740
|
+
)
|
|
741
|
+
capability_ceiling: tuple[str, ...] = (
|
|
742
|
+
"concerns:read", "directives:read", "memory:read",
|
|
743
|
+
"projects:read", "reasoning", "situation:read", "web:read",
|
|
744
|
+
"world_model:read",
|
|
745
|
+
)
|
|
746
|
+
capability_deny: tuple[str, ...] = ("messaging:send", "root:shell")
|
|
747
|
+
required_boundary_refs: tuple[str, ...] = ()
|
|
748
|
+
allowed_shareability: tuple[str, ...] = ("owner_private",)
|
|
749
|
+
allowed_recipient_ids: tuple[str, ...] = ()
|
|
750
|
+
allow_destructive: bool = False
|
|
751
|
+
allow_root_shell: bool = False
|
|
752
|
+
allow_messaging: bool = False
|
|
753
|
+
schema: str = "CharterAdmissionConstraintsV1"
|
|
754
|
+
version: int = 1
|
|
755
|
+
|
|
756
|
+
def __post_init__(self) -> None:
|
|
757
|
+
if self.schema != "CharterAdmissionConstraintsV1" or self.version != 1:
|
|
758
|
+
raise DriveGovernanceError(
|
|
759
|
+
"unsupported_schema", "charter admission constraints are unsupported",
|
|
760
|
+
)
|
|
761
|
+
for field_name in ("objective_allow_terms", "objective_deny_terms"):
|
|
762
|
+
values = tuple(getattr(self, field_name))
|
|
763
|
+
if len(values) > 30:
|
|
764
|
+
raise DriveGovernanceError(
|
|
765
|
+
"invalid_admission_constraints", f"{field_name} exceeds 30 items",
|
|
766
|
+
)
|
|
767
|
+
canonical = tuple(sorted({
|
|
768
|
+
_text(item, field_name, 80).casefold() for item in values
|
|
769
|
+
}))
|
|
770
|
+
if values != canonical:
|
|
771
|
+
raise DriveGovernanceError(
|
|
772
|
+
"invalid_admission_constraints",
|
|
773
|
+
f"{field_name} must be unique, sorted, and lowercase",
|
|
774
|
+
)
|
|
775
|
+
for field_name in ("capability_ceiling", "capability_deny"):
|
|
776
|
+
values = tuple(getattr(self, field_name))
|
|
777
|
+
if len(values) > 30:
|
|
778
|
+
raise DriveGovernanceError(
|
|
779
|
+
"invalid_admission_constraints", f"{field_name} exceeds 30 items",
|
|
780
|
+
)
|
|
781
|
+
canonical = tuple(sorted({
|
|
782
|
+
_identifier(item, field_name) for item in values
|
|
783
|
+
}))
|
|
784
|
+
if values != canonical:
|
|
785
|
+
raise DriveGovernanceError(
|
|
786
|
+
"invalid_admission_constraints",
|
|
787
|
+
f"{field_name} must be unique and sorted",
|
|
788
|
+
)
|
|
789
|
+
boundaries = _refs(
|
|
790
|
+
self.required_boundary_refs, required=False, maximum=30,
|
|
791
|
+
)
|
|
792
|
+
if boundaries != self.required_boundary_refs:
|
|
793
|
+
raise DriveGovernanceError(
|
|
794
|
+
"invalid_admission_constraints",
|
|
795
|
+
"required boundary references are not canonical",
|
|
796
|
+
)
|
|
797
|
+
shareability = tuple(self.allowed_shareability)
|
|
798
|
+
if (
|
|
799
|
+
not shareability
|
|
800
|
+
or len(shareability) > len(_SHAREABILITY)
|
|
801
|
+
or tuple(sorted(set(shareability))) != shareability
|
|
802
|
+
or any(item not in _SHAREABILITY for item in shareability)
|
|
803
|
+
):
|
|
804
|
+
raise DriveGovernanceError(
|
|
805
|
+
"invalid_admission_constraints",
|
|
806
|
+
"allowed shareability lanes are invalid",
|
|
807
|
+
)
|
|
808
|
+
recipients = tuple(self.allowed_recipient_ids)
|
|
809
|
+
if len(recipients) > 30 or tuple(sorted(set(recipients))) != recipients:
|
|
810
|
+
raise DriveGovernanceError(
|
|
811
|
+
"invalid_admission_constraints", "recipient IDs are not canonical",
|
|
812
|
+
)
|
|
813
|
+
for recipient in recipients:
|
|
814
|
+
_identifier(recipient, "allowed_recipient_id", maximum=128)
|
|
815
|
+
if any(not isinstance(value, bool) for value in (
|
|
816
|
+
self.allow_destructive, self.allow_root_shell, self.allow_messaging,
|
|
817
|
+
)):
|
|
818
|
+
raise DriveGovernanceError(
|
|
819
|
+
"invalid_admission_constraints", "admission flags must be boolean",
|
|
820
|
+
)
|
|
821
|
+
|
|
822
|
+
def payload(self) -> Dict[str, Any]:
|
|
823
|
+
return {
|
|
824
|
+
"schema": self.schema,
|
|
825
|
+
"version": self.version,
|
|
826
|
+
"objective_allow_terms": list(self.objective_allow_terms),
|
|
827
|
+
"objective_deny_terms": list(self.objective_deny_terms),
|
|
828
|
+
"capability_ceiling": list(self.capability_ceiling),
|
|
829
|
+
"capability_deny": list(self.capability_deny),
|
|
830
|
+
"required_boundary_refs": list(self.required_boundary_refs),
|
|
831
|
+
"allowed_shareability": list(self.allowed_shareability),
|
|
832
|
+
"allowed_recipient_ids": list(self.allowed_recipient_ids),
|
|
833
|
+
"allow_destructive": self.allow_destructive,
|
|
834
|
+
"allow_root_shell": self.allow_root_shell,
|
|
835
|
+
"allow_messaging": self.allow_messaging,
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
@classmethod
|
|
839
|
+
def from_payload(
|
|
840
|
+
cls, payload: Mapping[str, Any],
|
|
841
|
+
) -> "CharterAdmissionConstraintsV1":
|
|
842
|
+
return cls(
|
|
843
|
+
objective_allow_terms=tuple(payload.get("objective_allow_terms") or ()),
|
|
844
|
+
objective_deny_terms=tuple(payload.get("objective_deny_terms") or ()),
|
|
845
|
+
capability_ceiling=tuple(payload.get("capability_ceiling") or ()),
|
|
846
|
+
capability_deny=tuple(payload.get("capability_deny") or ()),
|
|
847
|
+
required_boundary_refs=tuple(
|
|
848
|
+
payload.get("required_boundary_refs") or ()),
|
|
849
|
+
allowed_shareability=tuple(payload.get("allowed_shareability") or ()),
|
|
850
|
+
allowed_recipient_ids=tuple(
|
|
851
|
+
payload.get("allowed_recipient_ids") or ()),
|
|
852
|
+
allow_destructive=payload.get("allow_destructive") is True,
|
|
853
|
+
allow_root_shell=payload.get("allow_root_shell") is True,
|
|
854
|
+
allow_messaging=payload.get("allow_messaging") is True,
|
|
855
|
+
schema=str(payload.get("schema") or ""),
|
|
856
|
+
version=int(payload.get("version") or 0),
|
|
857
|
+
)
|
|
858
|
+
|
|
859
|
+
|
|
860
|
+
@dataclass(frozen=True)
|
|
861
|
+
class CharterRevisionV1:
|
|
862
|
+
revision_id: str
|
|
863
|
+
content_digest: str
|
|
864
|
+
charter_key: str
|
|
865
|
+
revision_label: str
|
|
866
|
+
parent_revision_id: Optional[str]
|
|
867
|
+
title: str
|
|
868
|
+
purpose_summary: str
|
|
869
|
+
principles: tuple[str, ...]
|
|
870
|
+
drive_weights: tuple[tuple[str, float], ...]
|
|
871
|
+
ranking_budget: RankingBudgetV1
|
|
872
|
+
scope: ScopeV1
|
|
873
|
+
evidence_refs: tuple[str, ...]
|
|
874
|
+
proposed_by: str
|
|
875
|
+
proposed_at: str
|
|
876
|
+
expires_at: str
|
|
877
|
+
admission_constraints: Optional[CharterAdmissionConstraintsV1] = None
|
|
878
|
+
schema: str = "CharterRevisionV1"
|
|
879
|
+
version: int = 1
|
|
880
|
+
|
|
881
|
+
@classmethod
|
|
882
|
+
def create(
|
|
883
|
+
cls,
|
|
884
|
+
*,
|
|
885
|
+
charter_key: str,
|
|
886
|
+
revision_label: str,
|
|
887
|
+
parent_revision_id: Optional[str],
|
|
888
|
+
title: str,
|
|
889
|
+
purpose_summary: str,
|
|
890
|
+
principles: Iterable[str],
|
|
891
|
+
drive_weights: Mapping[str, float],
|
|
892
|
+
ranking_budget: RankingBudgetV1,
|
|
893
|
+
scope: ScopeV1,
|
|
894
|
+
evidence_refs: Iterable[str],
|
|
895
|
+
proposed_by: str,
|
|
896
|
+
proposed_at: datetime,
|
|
897
|
+
expires_at: datetime,
|
|
898
|
+
admission_constraints: Optional[CharterAdmissionConstraintsV1] = None,
|
|
899
|
+
) -> "CharterRevisionV1":
|
|
900
|
+
key = str(charter_key or "").strip().lower()
|
|
901
|
+
if not _SAFE_KEY.fullmatch(key):
|
|
902
|
+
raise DriveGovernanceError(
|
|
903
|
+
"invalid_charter_key", "charter key is invalid",
|
|
904
|
+
)
|
|
905
|
+
label = _identifier(revision_label, "revision_label", maximum=64)
|
|
906
|
+
parent = (
|
|
907
|
+
_identifier(parent_revision_id, "parent_revision_id")
|
|
908
|
+
if parent_revision_id else None
|
|
909
|
+
)
|
|
910
|
+
normalized_principles = tuple(
|
|
911
|
+
_text(item, "principle", 300) for item in principles or ()
|
|
912
|
+
)
|
|
913
|
+
if not normalized_principles or len(normalized_principles) > 20:
|
|
914
|
+
raise DriveGovernanceError(
|
|
915
|
+
"invalid_principles", "charter requires 1 to 20 principles",
|
|
916
|
+
)
|
|
917
|
+
if len(set(normalized_principles)) != len(normalized_principles):
|
|
918
|
+
raise DriveGovernanceError(
|
|
919
|
+
"invalid_principles", "charter principles must be unique",
|
|
920
|
+
)
|
|
921
|
+
weights: list[tuple[str, float]] = []
|
|
922
|
+
for raw_drive_id, raw_weight in (drive_weights or {}).items():
|
|
923
|
+
drive_id = _identifier(raw_drive_id, "drive_id")
|
|
924
|
+
weight = _bounded_float(
|
|
925
|
+
raw_weight, f"drive weight {drive_id}", minimum=0.000001,
|
|
926
|
+
maximum=1.0,
|
|
927
|
+
)
|
|
928
|
+
weights.append((drive_id, weight))
|
|
929
|
+
weights.sort()
|
|
930
|
+
if not weights or len(weights) > 20:
|
|
931
|
+
raise DriveGovernanceError(
|
|
932
|
+
"invalid_drive_weights", "charter requires 1 to 20 drive weights",
|
|
933
|
+
)
|
|
934
|
+
if sum(weight for _, weight in weights) > 1.00000001:
|
|
935
|
+
raise DriveGovernanceError(
|
|
936
|
+
"drive_budget_exceeded", "drive weights must sum to at most 1.0",
|
|
937
|
+
)
|
|
938
|
+
proposed = _as_utc(proposed_at)
|
|
939
|
+
expiry = _as_utc(expires_at)
|
|
940
|
+
if expiry - proposed < timedelta(hours=1):
|
|
941
|
+
raise DriveGovernanceError(
|
|
942
|
+
"invalid_expiry", "charter lifetime must be at least one hour",
|
|
943
|
+
)
|
|
944
|
+
if expiry - proposed > timedelta(days=366):
|
|
945
|
+
raise DriveGovernanceError(
|
|
946
|
+
"invalid_expiry", "charter lifetime cannot exceed 366 days",
|
|
947
|
+
)
|
|
948
|
+
constraints = admission_constraints or CharterAdmissionConstraintsV1()
|
|
949
|
+
authority = {
|
|
950
|
+
"schema": "CharterRevisionV1", "version": 1,
|
|
951
|
+
"charter_key": key,
|
|
952
|
+
"revision_label": label,
|
|
953
|
+
"parent_revision_id": parent,
|
|
954
|
+
"title": _text(title, "title", 120),
|
|
955
|
+
"purpose_summary": _text(
|
|
956
|
+
purpose_summary, "purpose_summary", 600,
|
|
957
|
+
),
|
|
958
|
+
"principles": list(normalized_principles),
|
|
959
|
+
"drive_weights": [[drive_id, weight] for drive_id, weight in weights],
|
|
960
|
+
"ranking_budget": ranking_budget.payload(),
|
|
961
|
+
"scope": scope.payload(),
|
|
962
|
+
"evidence_refs": list(_refs(
|
|
963
|
+
evidence_refs, required=True, maximum=30,
|
|
964
|
+
)),
|
|
965
|
+
"proposed_by": _identifier(
|
|
966
|
+
proposed_by, "proposed_by", maximum=128,
|
|
967
|
+
),
|
|
968
|
+
"proposed_at": _iso(proposed),
|
|
969
|
+
"expires_at": _iso(expiry),
|
|
970
|
+
"admission_constraints": constraints.payload(),
|
|
971
|
+
}
|
|
972
|
+
digest = _digest(authority)
|
|
973
|
+
return cls(
|
|
974
|
+
revision_id=f"charter:{key}:{digest[:24]}",
|
|
975
|
+
content_digest=digest,
|
|
976
|
+
charter_key=key,
|
|
977
|
+
revision_label=label,
|
|
978
|
+
parent_revision_id=parent,
|
|
979
|
+
title=authority["title"],
|
|
980
|
+
purpose_summary=authority["purpose_summary"],
|
|
981
|
+
principles=normalized_principles,
|
|
982
|
+
drive_weights=tuple(weights),
|
|
983
|
+
ranking_budget=ranking_budget,
|
|
984
|
+
scope=scope,
|
|
985
|
+
evidence_refs=tuple(authority["evidence_refs"]),
|
|
986
|
+
proposed_by=authority["proposed_by"],
|
|
987
|
+
proposed_at=authority["proposed_at"],
|
|
988
|
+
expires_at=authority["expires_at"],
|
|
989
|
+
admission_constraints=constraints,
|
|
990
|
+
)
|
|
991
|
+
|
|
992
|
+
def authority_payload(self) -> Dict[str, Any]:
|
|
993
|
+
payload = {
|
|
994
|
+
"schema": self.schema, "version": self.version,
|
|
995
|
+
"charter_key": self.charter_key,
|
|
996
|
+
"revision_label": self.revision_label,
|
|
997
|
+
"parent_revision_id": self.parent_revision_id,
|
|
998
|
+
"title": self.title,
|
|
999
|
+
"purpose_summary": self.purpose_summary,
|
|
1000
|
+
"principles": list(self.principles),
|
|
1001
|
+
"drive_weights": [list(item) for item in self.drive_weights],
|
|
1002
|
+
"ranking_budget": self.ranking_budget.payload(),
|
|
1003
|
+
"scope": self.scope.payload(),
|
|
1004
|
+
"evidence_refs": list(self.evidence_refs),
|
|
1005
|
+
"proposed_by": self.proposed_by,
|
|
1006
|
+
"proposed_at": self.proposed_at,
|
|
1007
|
+
"expires_at": self.expires_at,
|
|
1008
|
+
}
|
|
1009
|
+
if self.admission_constraints is not None:
|
|
1010
|
+
payload["admission_constraints"] = self.admission_constraints.payload()
|
|
1011
|
+
return payload
|
|
1012
|
+
|
|
1013
|
+
def payload(self) -> Dict[str, Any]:
|
|
1014
|
+
return {
|
|
1015
|
+
**self.authority_payload(),
|
|
1016
|
+
"revision_id": self.revision_id,
|
|
1017
|
+
"content_digest": self.content_digest,
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
def validate_integrity(self) -> None:
|
|
1021
|
+
if self.schema != "CharterRevisionV1" or self.version != 1:
|
|
1022
|
+
raise DriveGovernanceError(
|
|
1023
|
+
"unsupported_schema", "charter revision schema is unsupported",
|
|
1024
|
+
)
|
|
1025
|
+
if not _SAFE_KEY.fullmatch(str(self.charter_key or "")):
|
|
1026
|
+
raise DriveGovernanceError(
|
|
1027
|
+
"invalid_charter_key", "charter key is invalid",
|
|
1028
|
+
)
|
|
1029
|
+
_identifier(self.revision_label, "revision_label", maximum=64)
|
|
1030
|
+
if self.parent_revision_id:
|
|
1031
|
+
_identifier(self.parent_revision_id, "parent_revision_id")
|
|
1032
|
+
if _text(self.title, "title", 120) != self.title or _text(
|
|
1033
|
+
self.purpose_summary, "purpose_summary", 600,
|
|
1034
|
+
) != self.purpose_summary:
|
|
1035
|
+
raise DriveGovernanceError(
|
|
1036
|
+
"invalid_text", "charter text is not normalized and bounded",
|
|
1037
|
+
)
|
|
1038
|
+
if not 1 <= len(self.principles) <= 20 or len(
|
|
1039
|
+
set(self.principles)
|
|
1040
|
+
) != len(self.principles):
|
|
1041
|
+
raise DriveGovernanceError(
|
|
1042
|
+
"invalid_principles", "charter principles are invalid",
|
|
1043
|
+
)
|
|
1044
|
+
if any(_text(item, "principle", 300) != item for item in self.principles):
|
|
1045
|
+
raise DriveGovernanceError(
|
|
1046
|
+
"invalid_principles", "charter principles are not canonical",
|
|
1047
|
+
)
|
|
1048
|
+
if not 1 <= len(self.drive_weights) <= 20:
|
|
1049
|
+
raise DriveGovernanceError(
|
|
1050
|
+
"invalid_drive_weights", "charter drive weights are invalid",
|
|
1051
|
+
)
|
|
1052
|
+
drive_ids = [item[0] for item in self.drive_weights]
|
|
1053
|
+
if drive_ids != sorted(drive_ids) or len(set(drive_ids)) != len(drive_ids):
|
|
1054
|
+
raise DriveGovernanceError(
|
|
1055
|
+
"invalid_drive_weights", "charter drive weights are not canonical",
|
|
1056
|
+
)
|
|
1057
|
+
for drive_id, weight in self.drive_weights:
|
|
1058
|
+
_identifier(drive_id, "drive_id")
|
|
1059
|
+
_bounded_float(
|
|
1060
|
+
weight, f"drive weight {drive_id}", minimum=0.000001, maximum=1.0,
|
|
1061
|
+
)
|
|
1062
|
+
if sum(weight for _, weight in self.drive_weights) > 1.00000001:
|
|
1063
|
+
raise DriveGovernanceError(
|
|
1064
|
+
"drive_budget_exceeded", "drive weights exceed the charter budget",
|
|
1065
|
+
)
|
|
1066
|
+
if _refs(
|
|
1067
|
+
self.evidence_refs, required=True, maximum=30,
|
|
1068
|
+
) != self.evidence_refs:
|
|
1069
|
+
raise DriveGovernanceError(
|
|
1070
|
+
"invalid_evidence", "charter evidence references are not canonical",
|
|
1071
|
+
)
|
|
1072
|
+
if self.admission_constraints is not None:
|
|
1073
|
+
self.admission_constraints.__post_init__()
|
|
1074
|
+
_identifier(self.proposed_by, "proposed_by", maximum=128)
|
|
1075
|
+
proposed = _parse_time(self.proposed_at, field="charter proposed_at")
|
|
1076
|
+
expiry = _parse_time(self.expires_at, field="charter expires_at")
|
|
1077
|
+
if not timedelta(hours=1) <= expiry - proposed <= timedelta(days=366):
|
|
1078
|
+
raise DriveGovernanceError(
|
|
1079
|
+
"invalid_expiry", "charter expiry is outside its bounded lifetime",
|
|
1080
|
+
)
|
|
1081
|
+
digest = _digest(self.authority_payload())
|
|
1082
|
+
if digest != self.content_digest or self.revision_id != (
|
|
1083
|
+
f"charter:{self.charter_key}:{digest[:24]}"
|
|
1084
|
+
):
|
|
1085
|
+
raise DriveGovernanceError(
|
|
1086
|
+
"immutable_object_tampered", "charter revision integrity mismatch",
|
|
1087
|
+
)
|
|
1088
|
+
|
|
1089
|
+
def weight_map(self) -> Dict[str, float]:
|
|
1090
|
+
return dict(self.drive_weights)
|
|
1091
|
+
|
|
1092
|
+
@classmethod
|
|
1093
|
+
def from_payload(cls, payload: Mapping[str, Any]) -> "CharterRevisionV1":
|
|
1094
|
+
result = cls(
|
|
1095
|
+
revision_id=str(payload["revision_id"]),
|
|
1096
|
+
content_digest=str(payload["content_digest"]),
|
|
1097
|
+
charter_key=str(payload["charter_key"]),
|
|
1098
|
+
revision_label=str(payload["revision_label"]),
|
|
1099
|
+
parent_revision_id=(
|
|
1100
|
+
str(payload["parent_revision_id"])
|
|
1101
|
+
if payload.get("parent_revision_id") else None
|
|
1102
|
+
),
|
|
1103
|
+
title=str(payload["title"]),
|
|
1104
|
+
purpose_summary=str(payload["purpose_summary"]),
|
|
1105
|
+
principles=tuple(payload.get("principles") or ()),
|
|
1106
|
+
drive_weights=tuple(
|
|
1107
|
+
(str(item[0]), float(item[1]))
|
|
1108
|
+
for item in payload.get("drive_weights") or ()
|
|
1109
|
+
),
|
|
1110
|
+
ranking_budget=RankingBudgetV1(**dict(payload["ranking_budget"])),
|
|
1111
|
+
scope=ScopeV1(**dict(payload["scope"])),
|
|
1112
|
+
evidence_refs=tuple(payload.get("evidence_refs") or ()),
|
|
1113
|
+
proposed_by=str(payload["proposed_by"]),
|
|
1114
|
+
proposed_at=str(payload["proposed_at"]),
|
|
1115
|
+
expires_at=str(payload["expires_at"]),
|
|
1116
|
+
admission_constraints=(
|
|
1117
|
+
CharterAdmissionConstraintsV1.from_payload(
|
|
1118
|
+
dict(payload["admission_constraints"]),
|
|
1119
|
+
)
|
|
1120
|
+
if payload.get("admission_constraints") else None
|
|
1121
|
+
),
|
|
1122
|
+
)
|
|
1123
|
+
result.validate_integrity()
|
|
1124
|
+
return result
|
|
1125
|
+
|
|
1126
|
+
|
|
1127
|
+
@dataclass(frozen=True)
|
|
1128
|
+
class GoalRankInputV1:
|
|
1129
|
+
"""Server-built projection of a P3-created project eligible for ordering."""
|
|
1130
|
+
|
|
1131
|
+
goal_id: str
|
|
1132
|
+
proposal_id: str
|
|
1133
|
+
goal_fingerprint: str
|
|
1134
|
+
title: str
|
|
1135
|
+
objective_summary: str
|
|
1136
|
+
rationale_summary: str
|
|
1137
|
+
evidence_refs: tuple[str, ...]
|
|
1138
|
+
policy_decision_refs: tuple[str, ...]
|
|
1139
|
+
scope: ScopeV1
|
|
1140
|
+
schema: str = "GoalRankInputV1"
|
|
1141
|
+
version: int = 1
|
|
1142
|
+
|
|
1143
|
+
def __post_init__(self) -> None:
|
|
1144
|
+
_identifier(self.goal_id, "goal_id")
|
|
1145
|
+
_identifier(self.proposal_id, "proposal_id")
|
|
1146
|
+
_identifier(self.goal_fingerprint, "goal_fingerprint")
|
|
1147
|
+
_text(self.title, "title", 160)
|
|
1148
|
+
_text(self.objective_summary, "objective_summary", 600)
|
|
1149
|
+
_text(self.rationale_summary, "rationale_summary", 500)
|
|
1150
|
+
_refs(self.evidence_refs, maximum=30)
|
|
1151
|
+
refs = _refs(
|
|
1152
|
+
self.policy_decision_refs,
|
|
1153
|
+
field="policy_decision_refs",
|
|
1154
|
+
maximum=20,
|
|
1155
|
+
)
|
|
1156
|
+
if len(refs) != len(self.policy_decision_refs):
|
|
1157
|
+
raise DriveGovernanceError(
|
|
1158
|
+
"duplicate_policy_reference", "policy decision references repeat",
|
|
1159
|
+
)
|
|
1160
|
+
|
|
1161
|
+
def payload(self) -> Dict[str, Any]:
|
|
1162
|
+
return {
|
|
1163
|
+
"schema": self.schema, "version": self.version,
|
|
1164
|
+
"goal_id": self.goal_id,
|
|
1165
|
+
"proposal_id": self.proposal_id,
|
|
1166
|
+
"goal_fingerprint": self.goal_fingerprint,
|
|
1167
|
+
"title": self.title,
|
|
1168
|
+
"objective_summary": self.objective_summary,
|
|
1169
|
+
"rationale_summary": self.rationale_summary,
|
|
1170
|
+
"evidence_refs": list(self.evidence_refs),
|
|
1171
|
+
"policy_decision_refs": list(self.policy_decision_refs),
|
|
1172
|
+
"scope": self.scope.payload(),
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
|
|
1176
|
+
@dataclass(frozen=True)
|
|
1177
|
+
class DriveContributionV1:
|
|
1178
|
+
drive_id: str
|
|
1179
|
+
state: str
|
|
1180
|
+
weight: float
|
|
1181
|
+
raw_signal_value: float
|
|
1182
|
+
mean_confidence: float
|
|
1183
|
+
weighted_value: float
|
|
1184
|
+
abs_budget: float
|
|
1185
|
+
signal_refs: tuple[str, ...]
|
|
1186
|
+
evidence_refs: tuple[str, ...]
|
|
1187
|
+
rationale_summaries: tuple[str, ...]
|
|
1188
|
+
schema: str = "DriveContributionV1"
|
|
1189
|
+
version: int = 1
|
|
1190
|
+
|
|
1191
|
+
def payload(self) -> Dict[str, Any]:
|
|
1192
|
+
return {
|
|
1193
|
+
"schema": self.schema, "version": self.version,
|
|
1194
|
+
"drive_id": self.drive_id,
|
|
1195
|
+
"state": self.state,
|
|
1196
|
+
"weight": self.weight,
|
|
1197
|
+
"raw_signal_value": self.raw_signal_value,
|
|
1198
|
+
"mean_confidence": self.mean_confidence,
|
|
1199
|
+
"weighted_value": self.weighted_value,
|
|
1200
|
+
"abs_budget": self.abs_budget,
|
|
1201
|
+
"signal_refs": list(self.signal_refs),
|
|
1202
|
+
"evidence_refs": list(self.evidence_refs),
|
|
1203
|
+
"rationale_summaries": list(self.rationale_summaries),
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
|
|
1207
|
+
@dataclass(frozen=True)
|
|
1208
|
+
class GoalRankResultV1:
|
|
1209
|
+
goal_id: str
|
|
1210
|
+
proposal_id: str
|
|
1211
|
+
state: str
|
|
1212
|
+
eligible: bool
|
|
1213
|
+
total_score: Optional[float]
|
|
1214
|
+
contributions: tuple[DriveContributionV1, ...]
|
|
1215
|
+
drive_states: Dict[str, str]
|
|
1216
|
+
evidence_refs: tuple[str, ...]
|
|
1217
|
+
policy_decision_refs: tuple[str, ...]
|
|
1218
|
+
rationale_summary: str
|
|
1219
|
+
scope: ScopeV1
|
|
1220
|
+
authorization_effect: str = "none"
|
|
1221
|
+
schema: str = "GoalRankResultV1"
|
|
1222
|
+
version: int = 1
|
|
1223
|
+
|
|
1224
|
+
def payload(self) -> Dict[str, Any]:
|
|
1225
|
+
return {
|
|
1226
|
+
"schema": self.schema, "version": self.version,
|
|
1227
|
+
"goal_id": self.goal_id,
|
|
1228
|
+
"proposal_id": self.proposal_id,
|
|
1229
|
+
"state": self.state,
|
|
1230
|
+
"eligible": self.eligible,
|
|
1231
|
+
"total_score": self.total_score,
|
|
1232
|
+
"contributions": [item.payload() for item in self.contributions],
|
|
1233
|
+
"drive_states": dict(sorted(self.drive_states.items())),
|
|
1234
|
+
"evidence_refs": list(self.evidence_refs),
|
|
1235
|
+
"policy_decision_refs": list(self.policy_decision_refs),
|
|
1236
|
+
"rationale_summary": self.rationale_summary,
|
|
1237
|
+
"scope": self.scope.payload(),
|
|
1238
|
+
"authorization_effect": self.authorization_effect,
|
|
1239
|
+
"required_p3_stages": list(_REQUIRED_P3_STAGES),
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
|
|
1243
|
+
@dataclass(frozen=True)
|
|
1244
|
+
class RankingBatchV1:
|
|
1245
|
+
status: str
|
|
1246
|
+
mode: str
|
|
1247
|
+
ranking_applied: bool
|
|
1248
|
+
charter_revision_id: Optional[str]
|
|
1249
|
+
input_order: tuple[str, ...]
|
|
1250
|
+
suggested_order: tuple[str, ...]
|
|
1251
|
+
effective_order: tuple[str, ...]
|
|
1252
|
+
held_goal_ids: tuple[str, ...]
|
|
1253
|
+
results: tuple[GoalRankResultV1, ...]
|
|
1254
|
+
generated_at: str
|
|
1255
|
+
authorization_effect: str = "none"
|
|
1256
|
+
schema: str = "GoalRankingBatchV1"
|
|
1257
|
+
version: int = 1
|
|
1258
|
+
|
|
1259
|
+
def payload(self) -> Dict[str, Any]:
|
|
1260
|
+
return {
|
|
1261
|
+
"schema": self.schema, "version": self.version,
|
|
1262
|
+
"status": self.status,
|
|
1263
|
+
"mode": self.mode,
|
|
1264
|
+
"ranking_applied": self.ranking_applied,
|
|
1265
|
+
"charter_revision_id": self.charter_revision_id,
|
|
1266
|
+
"input_order": list(self.input_order),
|
|
1267
|
+
"suggested_order": list(self.suggested_order),
|
|
1268
|
+
"effective_order": list(self.effective_order),
|
|
1269
|
+
"held_goal_ids": list(self.held_goal_ids),
|
|
1270
|
+
"results": [item.payload() for item in self.results],
|
|
1271
|
+
"generated_at": self.generated_at,
|
|
1272
|
+
"authorization_effect": self.authorization_effect,
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
def observer_projection(
|
|
1276
|
+
self,
|
|
1277
|
+
*,
|
|
1278
|
+
viewer_person_id: str,
|
|
1279
|
+
owner_person_id: str,
|
|
1280
|
+
audiences: set[str] | frozenset[str] = frozenset(),
|
|
1281
|
+
) -> Dict[str, Any]:
|
|
1282
|
+
visible = tuple(
|
|
1283
|
+
item for item in self.results
|
|
1284
|
+
if item.scope.visible_to(
|
|
1285
|
+
viewer_person_id=viewer_person_id,
|
|
1286
|
+
owner_person_id=owner_person_id,
|
|
1287
|
+
audiences=audiences,
|
|
1288
|
+
)
|
|
1289
|
+
)
|
|
1290
|
+
ids = {item.goal_id for item in visible}
|
|
1291
|
+
payload = self.payload()
|
|
1292
|
+
payload["results"] = [item.payload() for item in visible]
|
|
1293
|
+
for key in ("input_order", "suggested_order", "effective_order"):
|
|
1294
|
+
payload[key] = [goal_id for goal_id in payload[key] if goal_id in ids]
|
|
1295
|
+
payload["held_goal_ids"] = [
|
|
1296
|
+
goal_id for goal_id in payload["held_goal_ids"] if goal_id in ids
|
|
1297
|
+
]
|
|
1298
|
+
return payload
|
|
1299
|
+
|
|
1300
|
+
|
|
1301
|
+
def charter_transition_binding(
|
|
1302
|
+
revision: CharterRevisionV1,
|
|
1303
|
+
*,
|
|
1304
|
+
transition: str,
|
|
1305
|
+
expected_active_revision_id: Optional[str],
|
|
1306
|
+
) -> ActionBinding:
|
|
1307
|
+
"""Build one exact, privacy-bounded ApprovalAuthority action binding."""
|
|
1308
|
+
|
|
1309
|
+
normalized = str(transition or "").strip().lower()
|
|
1310
|
+
if normalized not in _TRANSITIONS:
|
|
1311
|
+
raise DriveGovernanceError(
|
|
1312
|
+
"invalid_transition", "transition must be activate or revoke",
|
|
1313
|
+
)
|
|
1314
|
+
revision.validate_integrity()
|
|
1315
|
+
constraints = {
|
|
1316
|
+
"charter_key_digest": _digest(revision.charter_key),
|
|
1317
|
+
"revision_id_digest": _digest(revision.revision_id),
|
|
1318
|
+
"content_digest": revision.content_digest,
|
|
1319
|
+
"expected_active_revision_digest": _digest(
|
|
1320
|
+
expected_active_revision_id,
|
|
1321
|
+
),
|
|
1322
|
+
}
|
|
1323
|
+
scope = {
|
|
1324
|
+
"version": 1,
|
|
1325
|
+
"job_type": "charter_governance",
|
|
1326
|
+
"action_name": f"charter_revision_{normalized}",
|
|
1327
|
+
"risk": "authority_mutation",
|
|
1328
|
+
"constraints": constraints,
|
|
1329
|
+
}
|
|
1330
|
+
snapshot = {
|
|
1331
|
+
"version": 1,
|
|
1332
|
+
"transition": normalized,
|
|
1333
|
+
"revision_id": revision.revision_id,
|
|
1334
|
+
"content_digest": revision.content_digest,
|
|
1335
|
+
"expected_active_revision_id": expected_active_revision_id,
|
|
1336
|
+
"scope": scope,
|
|
1337
|
+
}
|
|
1338
|
+
return ActionBinding(
|
|
1339
|
+
action_digest=_digest(snapshot),
|
|
1340
|
+
scope=scope,
|
|
1341
|
+
scope_digest=_digest(scope),
|
|
1342
|
+
)
|
|
1343
|
+
|
|
1344
|
+
|
|
1345
|
+
def charter_transition_subject(
|
|
1346
|
+
revision: CharterRevisionV1,
|
|
1347
|
+
*,
|
|
1348
|
+
transition: str,
|
|
1349
|
+
) -> ApprovalSubjectBinding:
|
|
1350
|
+
normalized = str(transition or "").strip().lower()
|
|
1351
|
+
if normalized not in _TRANSITIONS:
|
|
1352
|
+
raise DriveGovernanceError(
|
|
1353
|
+
"invalid_transition", "transition must be activate or revoke",
|
|
1354
|
+
)
|
|
1355
|
+
revision.validate_integrity()
|
|
1356
|
+
return ApprovalSubjectBinding(
|
|
1357
|
+
kind="charter_transition",
|
|
1358
|
+
subject_id=revision.revision_id,
|
|
1359
|
+
revision=revision.content_digest,
|
|
1360
|
+
action=normalized,
|
|
1361
|
+
)
|
|
1362
|
+
|
|
1363
|
+
|
|
1364
|
+
def charter_transition_presentation(
|
|
1365
|
+
revision: CharterRevisionV1,
|
|
1366
|
+
*,
|
|
1367
|
+
transition: str,
|
|
1368
|
+
binding: ActionBinding,
|
|
1369
|
+
) -> Dict[str, Any]:
|
|
1370
|
+
"""Build the bounded owner view without charter principles or evidence."""
|
|
1371
|
+
|
|
1372
|
+
normalized = str(transition or "").strip().lower()
|
|
1373
|
+
if normalized not in _TRANSITIONS:
|
|
1374
|
+
raise DriveGovernanceError(
|
|
1375
|
+
"invalid_transition", "transition must be activate or revoke",
|
|
1376
|
+
)
|
|
1377
|
+
verb = "Activate" if normalized == "activate" else "Revoke"
|
|
1378
|
+
return {
|
|
1379
|
+
"schema": PRESENTATION_SCHEMA,
|
|
1380
|
+
"version": 1,
|
|
1381
|
+
"summary": (
|
|
1382
|
+
f"{verb} charter {revision.title} "
|
|
1383
|
+
f"({revision.revision_label})"
|
|
1384
|
+
),
|
|
1385
|
+
"action_name": f"charter_revision_{normalized}",
|
|
1386
|
+
"risk": "authority_mutation",
|
|
1387
|
+
"effect": "mutation",
|
|
1388
|
+
"target": f"charter:{revision.charter_key}",
|
|
1389
|
+
"capabilities": [],
|
|
1390
|
+
"deadline": "bounded by approval request expiry",
|
|
1391
|
+
"reversibility": (
|
|
1392
|
+
"revocable by a new exact owner decision"
|
|
1393
|
+
if normalized == "activate"
|
|
1394
|
+
else "requires a new charter activation decision"
|
|
1395
|
+
),
|
|
1396
|
+
"constraints": dict(binding.scope["constraints"]),
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
|
|
1400
|
+
class DriveGovernanceStore:
|
|
1401
|
+
"""Append-only SQLite ledger for drives, signals, and charter lifecycle."""
|
|
1402
|
+
|
|
1403
|
+
def __init__(self, db_path: str | os.PathLike[str]) -> None:
|
|
1404
|
+
self.path = Path(db_path)
|
|
1405
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
1406
|
+
if not self.path.exists():
|
|
1407
|
+
self.path.touch(mode=0o600)
|
|
1408
|
+
else:
|
|
1409
|
+
os.chmod(self.path, 0o600)
|
|
1410
|
+
self._lock = threading.RLock()
|
|
1411
|
+
self._conn = sqlite3.connect(str(self.path), check_same_thread=False)
|
|
1412
|
+
self._conn.row_factory = sqlite3.Row
|
|
1413
|
+
self._conn.execute("PRAGMA foreign_keys=ON")
|
|
1414
|
+
self._conn.execute("PRAGMA busy_timeout=30000")
|
|
1415
|
+
self._closed = False
|
|
1416
|
+
self._initialize()
|
|
1417
|
+
|
|
1418
|
+
def close(self) -> None:
|
|
1419
|
+
"""Release the feature-owned SQLite handle; exact retries are safe."""
|
|
1420
|
+
|
|
1421
|
+
with self._lock:
|
|
1422
|
+
if self._closed:
|
|
1423
|
+
return
|
|
1424
|
+
self._conn.close()
|
|
1425
|
+
self._closed = True
|
|
1426
|
+
|
|
1427
|
+
def _initialize(self) -> None:
|
|
1428
|
+
with self._lock, self._conn:
|
|
1429
|
+
self._conn.execute("PRAGMA journal_mode=WAL")
|
|
1430
|
+
self._conn.executescript(
|
|
1431
|
+
"""
|
|
1432
|
+
CREATE TABLE IF NOT EXISTS governance_operations (
|
|
1433
|
+
operation_id TEXT PRIMARY KEY,
|
|
1434
|
+
object_kind TEXT NOT NULL,
|
|
1435
|
+
object_id TEXT NOT NULL,
|
|
1436
|
+
content_digest TEXT NOT NULL,
|
|
1437
|
+
created_at TEXT NOT NULL
|
|
1438
|
+
);
|
|
1439
|
+
|
|
1440
|
+
CREATE TABLE IF NOT EXISTS drive_definitions (
|
|
1441
|
+
drive_id TEXT PRIMARY KEY,
|
|
1442
|
+
definition_digest TEXT NOT NULL,
|
|
1443
|
+
payload_json TEXT NOT NULL,
|
|
1444
|
+
created_at TEXT NOT NULL
|
|
1445
|
+
);
|
|
1446
|
+
|
|
1447
|
+
CREATE TABLE IF NOT EXISTS drive_signals (
|
|
1448
|
+
signal_seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1449
|
+
signal_id TEXT NOT NULL UNIQUE,
|
|
1450
|
+
signal_digest TEXT NOT NULL,
|
|
1451
|
+
drive_id TEXT NOT NULL,
|
|
1452
|
+
goal_fingerprint TEXT NOT NULL,
|
|
1453
|
+
state TEXT NOT NULL,
|
|
1454
|
+
observed_at TEXT NOT NULL,
|
|
1455
|
+
expires_at TEXT NOT NULL,
|
|
1456
|
+
payload_json TEXT NOT NULL,
|
|
1457
|
+
recorded_at TEXT NOT NULL,
|
|
1458
|
+
FOREIGN KEY(drive_id) REFERENCES drive_definitions(drive_id)
|
|
1459
|
+
);
|
|
1460
|
+
CREATE INDEX IF NOT EXISTS drive_signals_goal_idx
|
|
1461
|
+
ON drive_signals(goal_fingerprint, observed_at DESC, signal_seq);
|
|
1462
|
+
CREATE INDEX IF NOT EXISTS drive_signals_drive_idx
|
|
1463
|
+
ON drive_signals(drive_id, expires_at);
|
|
1464
|
+
|
|
1465
|
+
CREATE TABLE IF NOT EXISTS charter_revisions (
|
|
1466
|
+
revision_id TEXT PRIMARY KEY,
|
|
1467
|
+
charter_key TEXT NOT NULL,
|
|
1468
|
+
content_digest TEXT NOT NULL,
|
|
1469
|
+
parent_revision_id TEXT,
|
|
1470
|
+
proposed_at TEXT NOT NULL,
|
|
1471
|
+
expires_at TEXT NOT NULL,
|
|
1472
|
+
payload_json TEXT NOT NULL
|
|
1473
|
+
);
|
|
1474
|
+
CREATE INDEX IF NOT EXISTS charter_revisions_key_idx
|
|
1475
|
+
ON charter_revisions(charter_key, proposed_at DESC);
|
|
1476
|
+
|
|
1477
|
+
CREATE TABLE IF NOT EXISTS charter_lifecycle_events (
|
|
1478
|
+
event_seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
1479
|
+
event_id TEXT NOT NULL UNIQUE,
|
|
1480
|
+
charter_key TEXT NOT NULL,
|
|
1481
|
+
revision_id TEXT NOT NULL,
|
|
1482
|
+
transition TEXT NOT NULL,
|
|
1483
|
+
from_revision_id TEXT,
|
|
1484
|
+
action_digest TEXT NOT NULL,
|
|
1485
|
+
approval_request_id TEXT NOT NULL,
|
|
1486
|
+
decision_id TEXT NOT NULL,
|
|
1487
|
+
principal_id TEXT NOT NULL,
|
|
1488
|
+
authority_evidence TEXT NOT NULL,
|
|
1489
|
+
occurred_at TEXT NOT NULL,
|
|
1490
|
+
payload_json TEXT NOT NULL,
|
|
1491
|
+
FOREIGN KEY(revision_id) REFERENCES charter_revisions(revision_id)
|
|
1492
|
+
);
|
|
1493
|
+
CREATE INDEX IF NOT EXISTS charter_events_key_idx
|
|
1494
|
+
ON charter_lifecycle_events(charter_key, event_seq);
|
|
1495
|
+
CREATE INDEX IF NOT EXISTS charter_events_revision_idx
|
|
1496
|
+
ON charter_lifecycle_events(revision_id, event_seq);
|
|
1497
|
+
|
|
1498
|
+
CREATE TABLE IF NOT EXISTS charter_transition_operations (
|
|
1499
|
+
operation_id TEXT PRIMARY KEY,
|
|
1500
|
+
transition_digest TEXT NOT NULL,
|
|
1501
|
+
revision_id TEXT NOT NULL,
|
|
1502
|
+
transition TEXT NOT NULL,
|
|
1503
|
+
approval_request_id TEXT NOT NULL,
|
|
1504
|
+
event_id TEXT NOT NULL,
|
|
1505
|
+
created_at TEXT NOT NULL
|
|
1506
|
+
);
|
|
1507
|
+
|
|
1508
|
+
CREATE TABLE IF NOT EXISTS charter_authority_uses (
|
|
1509
|
+
approval_request_id TEXT PRIMARY KEY,
|
|
1510
|
+
action_digest TEXT NOT NULL,
|
|
1511
|
+
operation_id TEXT NOT NULL UNIQUE,
|
|
1512
|
+
event_id TEXT NOT NULL,
|
|
1513
|
+
used_at TEXT NOT NULL
|
|
1514
|
+
);
|
|
1515
|
+
"""
|
|
1516
|
+
)
|
|
1517
|
+
current = int(self._conn.execute("PRAGMA user_version").fetchone()[0])
|
|
1518
|
+
if current < DRIVE_GOVERNANCE_SCHEMA_VERSION:
|
|
1519
|
+
self._conn.execute(
|
|
1520
|
+
f"PRAGMA user_version={DRIVE_GOVERNANCE_SCHEMA_VERSION}"
|
|
1521
|
+
)
|
|
1522
|
+
os.chmod(self.path, 0o600)
|
|
1523
|
+
|
|
1524
|
+
def _operation(
|
|
1525
|
+
self,
|
|
1526
|
+
conn: sqlite3.Connection,
|
|
1527
|
+
*,
|
|
1528
|
+
operation_id: str,
|
|
1529
|
+
object_kind: str,
|
|
1530
|
+
object_id: str,
|
|
1531
|
+
content_digest: str,
|
|
1532
|
+
now: datetime,
|
|
1533
|
+
) -> bool:
|
|
1534
|
+
operation = _operation_id(operation_id)
|
|
1535
|
+
row = conn.execute(
|
|
1536
|
+
"SELECT * FROM governance_operations WHERE operation_id=?",
|
|
1537
|
+
(operation,),
|
|
1538
|
+
).fetchone()
|
|
1539
|
+
if row is not None:
|
|
1540
|
+
if (
|
|
1541
|
+
row["object_kind"] != object_kind
|
|
1542
|
+
or row["object_id"] != object_id
|
|
1543
|
+
or row["content_digest"] != content_digest
|
|
1544
|
+
):
|
|
1545
|
+
raise DriveGovernanceError(
|
|
1546
|
+
"operation_replay_conflict",
|
|
1547
|
+
"operation replay does not match its immutable first use",
|
|
1548
|
+
)
|
|
1549
|
+
return True
|
|
1550
|
+
conn.execute(
|
|
1551
|
+
"INSERT INTO governance_operations "
|
|
1552
|
+
"(operation_id,object_kind,object_id,content_digest,created_at) "
|
|
1553
|
+
"VALUES (?,?,?,?,?)",
|
|
1554
|
+
(operation, object_kind, object_id, content_digest, _iso(now)),
|
|
1555
|
+
)
|
|
1556
|
+
return False
|
|
1557
|
+
|
|
1558
|
+
def register_drive(
|
|
1559
|
+
self,
|
|
1560
|
+
drive: DriveV1,
|
|
1561
|
+
*,
|
|
1562
|
+
operation_id: str,
|
|
1563
|
+
now: Optional[datetime] = None,
|
|
1564
|
+
) -> Dict[str, Any]:
|
|
1565
|
+
observed = _as_utc(now)
|
|
1566
|
+
actual_digest = _digest(drive.authority_payload())
|
|
1567
|
+
with self._lock, self._conn:
|
|
1568
|
+
self._conn.execute("BEGIN IMMEDIATE")
|
|
1569
|
+
replayed = self._operation(
|
|
1570
|
+
self._conn,
|
|
1571
|
+
operation_id=operation_id,
|
|
1572
|
+
object_kind="drive",
|
|
1573
|
+
object_id=drive.drive_id,
|
|
1574
|
+
content_digest=actual_digest,
|
|
1575
|
+
now=observed,
|
|
1576
|
+
)
|
|
1577
|
+
if replayed:
|
|
1578
|
+
existing = self._conn.execute(
|
|
1579
|
+
"SELECT definition_digest,payload_json FROM drive_definitions "
|
|
1580
|
+
"WHERE drive_id=?", (drive.drive_id,),
|
|
1581
|
+
).fetchone()
|
|
1582
|
+
if existing is None or existing["definition_digest"] != actual_digest:
|
|
1583
|
+
raise DriveGovernanceError(
|
|
1584
|
+
"ledger_integrity_error", "replayed drive is missing or changed",
|
|
1585
|
+
)
|
|
1586
|
+
return {
|
|
1587
|
+
"enabled": True, "status": "drive_replayed",
|
|
1588
|
+
"drive_id": drive.drive_id, "replayed": True,
|
|
1589
|
+
}
|
|
1590
|
+
drive.validate_integrity()
|
|
1591
|
+
encoded = _canonical(drive.payload())
|
|
1592
|
+
existing = self._conn.execute(
|
|
1593
|
+
"SELECT * FROM drive_definitions WHERE drive_id=?",
|
|
1594
|
+
(drive.drive_id,),
|
|
1595
|
+
).fetchone()
|
|
1596
|
+
if existing is not None:
|
|
1597
|
+
if (
|
|
1598
|
+
existing["definition_digest"] != drive.definition_digest
|
|
1599
|
+
or existing["payload_json"] != encoded
|
|
1600
|
+
):
|
|
1601
|
+
raise DriveGovernanceError(
|
|
1602
|
+
"immutable_drive_conflict", "drive definition is immutable",
|
|
1603
|
+
)
|
|
1604
|
+
status = "drive_replayed"
|
|
1605
|
+
else:
|
|
1606
|
+
self._conn.execute(
|
|
1607
|
+
"INSERT INTO drive_definitions "
|
|
1608
|
+
"(drive_id,definition_digest,payload_json,created_at) "
|
|
1609
|
+
"VALUES (?,?,?,?)",
|
|
1610
|
+
(
|
|
1611
|
+
drive.drive_id, drive.definition_digest, encoded,
|
|
1612
|
+
_iso(observed),
|
|
1613
|
+
),
|
|
1614
|
+
)
|
|
1615
|
+
status = "drive_registered"
|
|
1616
|
+
return {
|
|
1617
|
+
"enabled": True, "status": status,
|
|
1618
|
+
"drive_id": drive.drive_id,
|
|
1619
|
+
"replayed": status == "drive_replayed",
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
def get_drive(self, drive_id: str) -> Optional[DriveV1]:
|
|
1623
|
+
with self._lock:
|
|
1624
|
+
row = self._conn.execute(
|
|
1625
|
+
"SELECT payload_json FROM drive_definitions WHERE drive_id=?",
|
|
1626
|
+
(str(drive_id),),
|
|
1627
|
+
).fetchone()
|
|
1628
|
+
return DriveV1.from_payload(json.loads(row["payload_json"])) if row else None
|
|
1629
|
+
|
|
1630
|
+
def list_drives(self) -> list[DriveV1]:
|
|
1631
|
+
with self._lock:
|
|
1632
|
+
rows = self._conn.execute(
|
|
1633
|
+
"SELECT payload_json FROM drive_definitions ORDER BY created_at,drive_id"
|
|
1634
|
+
).fetchall()
|
|
1635
|
+
return [DriveV1.from_payload(json.loads(row["payload_json"])) for row in rows]
|
|
1636
|
+
|
|
1637
|
+
def record_signal(
|
|
1638
|
+
self,
|
|
1639
|
+
signal: DriveSignalV1,
|
|
1640
|
+
*,
|
|
1641
|
+
operation_id: str,
|
|
1642
|
+
now: Optional[datetime] = None,
|
|
1643
|
+
) -> Dict[str, Any]:
|
|
1644
|
+
observed = _as_utc(now)
|
|
1645
|
+
actual_digest = _digest(signal.authority_payload())
|
|
1646
|
+
with self._lock, self._conn:
|
|
1647
|
+
self._conn.execute("BEGIN IMMEDIATE")
|
|
1648
|
+
replayed = self._operation(
|
|
1649
|
+
self._conn,
|
|
1650
|
+
operation_id=operation_id,
|
|
1651
|
+
object_kind="signal",
|
|
1652
|
+
object_id=signal.signal_id,
|
|
1653
|
+
content_digest=actual_digest,
|
|
1654
|
+
now=observed,
|
|
1655
|
+
)
|
|
1656
|
+
if replayed:
|
|
1657
|
+
existing = self._conn.execute(
|
|
1658
|
+
"SELECT signal_digest FROM drive_signals WHERE signal_id=?",
|
|
1659
|
+
(signal.signal_id,),
|
|
1660
|
+
).fetchone()
|
|
1661
|
+
if existing is None or existing["signal_digest"] != actual_digest:
|
|
1662
|
+
raise DriveGovernanceError(
|
|
1663
|
+
"ledger_integrity_error", "replayed signal is missing or changed",
|
|
1664
|
+
)
|
|
1665
|
+
return {
|
|
1666
|
+
"enabled": True, "status": "signal_replayed",
|
|
1667
|
+
"signal_id": signal.signal_id, "replayed": True,
|
|
1668
|
+
}
|
|
1669
|
+
signal.validate_integrity()
|
|
1670
|
+
drive = self.get_drive(signal.drive_id)
|
|
1671
|
+
if drive is None:
|
|
1672
|
+
raise DriveGovernanceError("drive_unknown", "signal drive is unknown")
|
|
1673
|
+
if not drive.scope.permits_child(signal.scope):
|
|
1674
|
+
raise DriveGovernanceError(
|
|
1675
|
+
"scope_broadening", "signal scope broadens its registered drive",
|
|
1676
|
+
)
|
|
1677
|
+
encoded = _canonical(signal.payload())
|
|
1678
|
+
existing = self._conn.execute(
|
|
1679
|
+
"SELECT * FROM drive_signals WHERE signal_id=?",
|
|
1680
|
+
(signal.signal_id,),
|
|
1681
|
+
).fetchone()
|
|
1682
|
+
if existing is not None:
|
|
1683
|
+
if (
|
|
1684
|
+
existing["signal_digest"] != signal.signal_digest
|
|
1685
|
+
or existing["payload_json"] != encoded
|
|
1686
|
+
):
|
|
1687
|
+
raise DriveGovernanceError(
|
|
1688
|
+
"immutable_signal_conflict", "drive signal is immutable",
|
|
1689
|
+
)
|
|
1690
|
+
status = "signal_replayed"
|
|
1691
|
+
else:
|
|
1692
|
+
self._conn.execute(
|
|
1693
|
+
"INSERT INTO drive_signals "
|
|
1694
|
+
"(signal_id,signal_digest,drive_id,goal_fingerprint,state,"
|
|
1695
|
+
"observed_at,expires_at,payload_json,recorded_at) "
|
|
1696
|
+
"VALUES (?,?,?,?,?,?,?,?,?)",
|
|
1697
|
+
(
|
|
1698
|
+
signal.signal_id, signal.signal_digest, signal.drive_id,
|
|
1699
|
+
signal.goal_fingerprint, signal.state, signal.observed_at,
|
|
1700
|
+
signal.expires_at, encoded, _iso(observed),
|
|
1701
|
+
),
|
|
1702
|
+
)
|
|
1703
|
+
status = "signal_recorded"
|
|
1704
|
+
return {
|
|
1705
|
+
"enabled": True, "status": status,
|
|
1706
|
+
"signal_id": signal.signal_id,
|
|
1707
|
+
"replayed": status == "signal_replayed",
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
def signals_for_goal(
|
|
1711
|
+
self,
|
|
1712
|
+
goal_fingerprint: str,
|
|
1713
|
+
*,
|
|
1714
|
+
now: Optional[datetime] = None,
|
|
1715
|
+
include_expired: bool = False,
|
|
1716
|
+
) -> list[DriveSignalV1]:
|
|
1717
|
+
with self._lock:
|
|
1718
|
+
rows = self._conn.execute(
|
|
1719
|
+
"SELECT payload_json FROM drive_signals "
|
|
1720
|
+
"WHERE goal_fingerprint=? "
|
|
1721
|
+
"ORDER BY observed_at DESC,signal_seq ASC",
|
|
1722
|
+
(str(goal_fingerprint),),
|
|
1723
|
+
).fetchall()
|
|
1724
|
+
signals = [
|
|
1725
|
+
DriveSignalV1.from_payload(json.loads(row["payload_json"]))
|
|
1726
|
+
for row in rows
|
|
1727
|
+
]
|
|
1728
|
+
if include_expired:
|
|
1729
|
+
return signals
|
|
1730
|
+
return [item for item in signals if item.state_at(now) != "expired"]
|
|
1731
|
+
|
|
1732
|
+
def propose_revision(
|
|
1733
|
+
self,
|
|
1734
|
+
revision: CharterRevisionV1,
|
|
1735
|
+
*,
|
|
1736
|
+
operation_id: str,
|
|
1737
|
+
now: Optional[datetime] = None,
|
|
1738
|
+
) -> Dict[str, Any]:
|
|
1739
|
+
observed = _as_utc(now)
|
|
1740
|
+
actual_digest = _digest(revision.authority_payload())
|
|
1741
|
+
with self._lock, self._conn:
|
|
1742
|
+
self._conn.execute("BEGIN IMMEDIATE")
|
|
1743
|
+
replayed = self._operation(
|
|
1744
|
+
self._conn,
|
|
1745
|
+
operation_id=operation_id,
|
|
1746
|
+
object_kind="charter_revision",
|
|
1747
|
+
object_id=revision.revision_id,
|
|
1748
|
+
content_digest=actual_digest,
|
|
1749
|
+
now=observed,
|
|
1750
|
+
)
|
|
1751
|
+
if replayed:
|
|
1752
|
+
existing = self._conn.execute(
|
|
1753
|
+
"SELECT content_digest FROM charter_revisions WHERE revision_id=?",
|
|
1754
|
+
(revision.revision_id,),
|
|
1755
|
+
).fetchone()
|
|
1756
|
+
if existing is None or existing["content_digest"] != actual_digest:
|
|
1757
|
+
raise DriveGovernanceError(
|
|
1758
|
+
"ledger_integrity_error",
|
|
1759
|
+
"replayed charter revision is missing or changed",
|
|
1760
|
+
)
|
|
1761
|
+
projection = self._revision_projection_conn(
|
|
1762
|
+
self._conn, revision.revision_id, observed,
|
|
1763
|
+
)
|
|
1764
|
+
projection.update({"enabled": True, "replayed": True})
|
|
1765
|
+
return projection
|
|
1766
|
+
revision.validate_integrity()
|
|
1767
|
+
if revision.parent_revision_id == revision.revision_id:
|
|
1768
|
+
raise DriveGovernanceError(
|
|
1769
|
+
"invalid_parent", "charter cannot be its own parent",
|
|
1770
|
+
)
|
|
1771
|
+
encoded = _canonical(revision.payload())
|
|
1772
|
+
existing = self._conn.execute(
|
|
1773
|
+
"SELECT * FROM charter_revisions WHERE revision_id=?",
|
|
1774
|
+
(revision.revision_id,),
|
|
1775
|
+
).fetchone()
|
|
1776
|
+
if existing is not None:
|
|
1777
|
+
if (
|
|
1778
|
+
existing["content_digest"] != revision.content_digest
|
|
1779
|
+
or existing["payload_json"] != encoded
|
|
1780
|
+
):
|
|
1781
|
+
raise DriveGovernanceError(
|
|
1782
|
+
"immutable_revision_conflict",
|
|
1783
|
+
"charter revision is immutable",
|
|
1784
|
+
)
|
|
1785
|
+
else:
|
|
1786
|
+
self._conn.execute(
|
|
1787
|
+
"INSERT INTO charter_revisions "
|
|
1788
|
+
"(revision_id,charter_key,content_digest,parent_revision_id,"
|
|
1789
|
+
"proposed_at,expires_at,payload_json) VALUES (?,?,?,?,?,?,?)",
|
|
1790
|
+
(
|
|
1791
|
+
revision.revision_id, revision.charter_key,
|
|
1792
|
+
revision.content_digest, revision.parent_revision_id,
|
|
1793
|
+
revision.proposed_at, revision.expires_at, encoded,
|
|
1794
|
+
),
|
|
1795
|
+
)
|
|
1796
|
+
projection = self._revision_projection_conn(
|
|
1797
|
+
self._conn, revision.revision_id, observed,
|
|
1798
|
+
)
|
|
1799
|
+
projection.update({"enabled": True, "replayed": existing is not None})
|
|
1800
|
+
return projection
|
|
1801
|
+
|
|
1802
|
+
def get_revision(self, revision_id: str) -> Optional[CharterRevisionV1]:
|
|
1803
|
+
with self._lock:
|
|
1804
|
+
row = self._conn.execute(
|
|
1805
|
+
"SELECT payload_json FROM charter_revisions WHERE revision_id=?",
|
|
1806
|
+
(str(revision_id),),
|
|
1807
|
+
).fetchone()
|
|
1808
|
+
return (
|
|
1809
|
+
CharterRevisionV1.from_payload(json.loads(row["payload_json"]))
|
|
1810
|
+
if row else None
|
|
1811
|
+
)
|
|
1812
|
+
|
|
1813
|
+
def _revision_projection_conn(
|
|
1814
|
+
self,
|
|
1815
|
+
conn: sqlite3.Connection,
|
|
1816
|
+
revision_id: str,
|
|
1817
|
+
now: datetime,
|
|
1818
|
+
) -> Dict[str, Any]:
|
|
1819
|
+
row = conn.execute(
|
|
1820
|
+
"SELECT payload_json FROM charter_revisions WHERE revision_id=?",
|
|
1821
|
+
(revision_id,),
|
|
1822
|
+
).fetchone()
|
|
1823
|
+
if row is None:
|
|
1824
|
+
raise DriveGovernanceError(
|
|
1825
|
+
"revision_unknown", "charter revision is unknown",
|
|
1826
|
+
)
|
|
1827
|
+
revision = CharterRevisionV1.from_payload(json.loads(row["payload_json"]))
|
|
1828
|
+
events = conn.execute(
|
|
1829
|
+
"SELECT * FROM charter_lifecycle_events "
|
|
1830
|
+
"WHERE revision_id=? ORDER BY event_seq",
|
|
1831
|
+
(revision_id,),
|
|
1832
|
+
).fetchall()
|
|
1833
|
+
for event in events:
|
|
1834
|
+
self._validate_lifecycle_event(event)
|
|
1835
|
+
transitions = [event["transition"] for event in events]
|
|
1836
|
+
if "revoke" in transitions:
|
|
1837
|
+
status = "revoked"
|
|
1838
|
+
elif "supersede" in transitions:
|
|
1839
|
+
status = "superseded"
|
|
1840
|
+
elif "activate" in transitions:
|
|
1841
|
+
status = (
|
|
1842
|
+
"expired"
|
|
1843
|
+
if _parse_time(revision.expires_at, field="revision expires_at") <= now
|
|
1844
|
+
else "active"
|
|
1845
|
+
)
|
|
1846
|
+
else:
|
|
1847
|
+
status = (
|
|
1848
|
+
"expired"
|
|
1849
|
+
if _parse_time(revision.expires_at, field="revision expires_at") <= now
|
|
1850
|
+
else "proposed"
|
|
1851
|
+
)
|
|
1852
|
+
result = revision.payload()
|
|
1853
|
+
result["lifecycle_status"] = status
|
|
1854
|
+
result["lifecycle_event_refs"] = [event["event_id"] for event in events]
|
|
1855
|
+
return result
|
|
1856
|
+
|
|
1857
|
+
@staticmethod
|
|
1858
|
+
def _validate_lifecycle_event(row: Mapping[str, Any]) -> Dict[str, Any]:
|
|
1859
|
+
try:
|
|
1860
|
+
payload = json.loads(str(row["payload_json"]))
|
|
1861
|
+
except (KeyError, TypeError, ValueError, json.JSONDecodeError) as exc:
|
|
1862
|
+
raise DriveGovernanceError(
|
|
1863
|
+
"lifecycle_integrity_error", "lifecycle event payload is invalid",
|
|
1864
|
+
) from exc
|
|
1865
|
+
fields = (
|
|
1866
|
+
"charter_key", "revision_id", "transition", "from_revision_id",
|
|
1867
|
+
"action_digest", "approval_request_id", "decision_id",
|
|
1868
|
+
"principal_id", "authority_evidence", "occurred_at",
|
|
1869
|
+
)
|
|
1870
|
+
if any(payload.get(field) != row[field] for field in fields):
|
|
1871
|
+
raise DriveGovernanceError(
|
|
1872
|
+
"lifecycle_integrity_error",
|
|
1873
|
+
"lifecycle event columns do not match immutable payload",
|
|
1874
|
+
)
|
|
1875
|
+
if row["event_id"] != f"charter-event:{_digest(payload)[:24]}":
|
|
1876
|
+
raise DriveGovernanceError(
|
|
1877
|
+
"lifecycle_integrity_error", "lifecycle event digest mismatch",
|
|
1878
|
+
)
|
|
1879
|
+
return payload
|
|
1880
|
+
|
|
1881
|
+
def revision_projection(
|
|
1882
|
+
self, revision_id: str, *, now: Optional[datetime] = None,
|
|
1883
|
+
) -> Dict[str, Any]:
|
|
1884
|
+
with self._lock:
|
|
1885
|
+
return self._revision_projection_conn(
|
|
1886
|
+
self._conn, revision_id, _as_utc(now),
|
|
1887
|
+
)
|
|
1888
|
+
|
|
1889
|
+
def list_revision_projections(
|
|
1890
|
+
self,
|
|
1891
|
+
charter_key: Optional[str] = None,
|
|
1892
|
+
*,
|
|
1893
|
+
now: Optional[datetime] = None,
|
|
1894
|
+
) -> list[Dict[str, Any]]:
|
|
1895
|
+
observed = _as_utc(now)
|
|
1896
|
+
with self._lock:
|
|
1897
|
+
if charter_key:
|
|
1898
|
+
rows = self._conn.execute(
|
|
1899
|
+
"SELECT revision_id FROM charter_revisions WHERE charter_key=? "
|
|
1900
|
+
"ORDER BY proposed_at DESC,revision_id",
|
|
1901
|
+
(str(charter_key),),
|
|
1902
|
+
).fetchall()
|
|
1903
|
+
else:
|
|
1904
|
+
rows = self._conn.execute(
|
|
1905
|
+
"SELECT revision_id FROM charter_revisions "
|
|
1906
|
+
"ORDER BY proposed_at DESC,revision_id"
|
|
1907
|
+
).fetchall()
|
|
1908
|
+
return [
|
|
1909
|
+
self._revision_projection_conn(
|
|
1910
|
+
self._conn, row["revision_id"], observed,
|
|
1911
|
+
)
|
|
1912
|
+
for row in rows
|
|
1913
|
+
]
|
|
1914
|
+
|
|
1915
|
+
def _active_revision_conn(
|
|
1916
|
+
self,
|
|
1917
|
+
conn: sqlite3.Connection,
|
|
1918
|
+
charter_key: str,
|
|
1919
|
+
now: datetime,
|
|
1920
|
+
) -> Optional[CharterRevisionV1]:
|
|
1921
|
+
rows = conn.execute(
|
|
1922
|
+
"SELECT revision_id,payload_json FROM charter_revisions "
|
|
1923
|
+
"WHERE charter_key=? ORDER BY proposed_at DESC,revision_id",
|
|
1924
|
+
(charter_key,),
|
|
1925
|
+
).fetchall()
|
|
1926
|
+
active: list[CharterRevisionV1] = []
|
|
1927
|
+
for row in rows:
|
|
1928
|
+
projection = self._revision_projection_conn(
|
|
1929
|
+
conn, row["revision_id"], now,
|
|
1930
|
+
)
|
|
1931
|
+
if projection["lifecycle_status"] == "active":
|
|
1932
|
+
active.append(CharterRevisionV1.from_payload(
|
|
1933
|
+
json.loads(row["payload_json"]),
|
|
1934
|
+
))
|
|
1935
|
+
if len(active) > 1:
|
|
1936
|
+
raise DriveGovernanceError(
|
|
1937
|
+
"lifecycle_integrity_error", "multiple active charter revisions",
|
|
1938
|
+
)
|
|
1939
|
+
return active[0] if active else None
|
|
1940
|
+
|
|
1941
|
+
def active_revision(
|
|
1942
|
+
self, charter_key: str = "default", *, now: Optional[datetime] = None,
|
|
1943
|
+
) -> Optional[CharterRevisionV1]:
|
|
1944
|
+
with self._lock:
|
|
1945
|
+
return self._active_revision_conn(
|
|
1946
|
+
self._conn, str(charter_key), _as_utc(now),
|
|
1947
|
+
)
|
|
1948
|
+
|
|
1949
|
+
def _active_revisions_conn(
|
|
1950
|
+
self, conn: sqlite3.Connection, now: datetime,
|
|
1951
|
+
) -> tuple[CharterRevisionV1, ...]:
|
|
1952
|
+
rows = conn.execute(
|
|
1953
|
+
"SELECT DISTINCT charter_key FROM charter_revisions "
|
|
1954
|
+
"ORDER BY charter_key"
|
|
1955
|
+
).fetchall()
|
|
1956
|
+
return tuple(
|
|
1957
|
+
active
|
|
1958
|
+
for row in rows
|
|
1959
|
+
for active in (
|
|
1960
|
+
self._active_revision_conn(conn, row["charter_key"], now),
|
|
1961
|
+
)
|
|
1962
|
+
if active is not None
|
|
1963
|
+
)
|
|
1964
|
+
|
|
1965
|
+
def active_revisions(
|
|
1966
|
+
self, *, now: Optional[datetime] = None,
|
|
1967
|
+
) -> tuple[CharterRevisionV1, ...]:
|
|
1968
|
+
"""Return every active charter across keys in deterministic order."""
|
|
1969
|
+
|
|
1970
|
+
with self._lock:
|
|
1971
|
+
return self._active_revisions_conn(self._conn, _as_utc(now))
|
|
1972
|
+
|
|
1973
|
+
def _validate_transition_conn(
|
|
1974
|
+
self,
|
|
1975
|
+
conn: sqlite3.Connection,
|
|
1976
|
+
revision: CharterRevisionV1,
|
|
1977
|
+
transition: str,
|
|
1978
|
+
now: datetime,
|
|
1979
|
+
) -> Optional[CharterRevisionV1]:
|
|
1980
|
+
current = self._active_revision_conn(conn, revision.charter_key, now)
|
|
1981
|
+
projection = self._revision_projection_conn(
|
|
1982
|
+
conn, revision.revision_id, now,
|
|
1983
|
+
)
|
|
1984
|
+
status = projection["lifecycle_status"]
|
|
1985
|
+
if transition == "activate":
|
|
1986
|
+
if status == "expired":
|
|
1987
|
+
raise DriveGovernanceError(
|
|
1988
|
+
"revision_expired", "expired charter revision cannot activate",
|
|
1989
|
+
)
|
|
1990
|
+
if status == "active":
|
|
1991
|
+
raise DriveGovernanceError(
|
|
1992
|
+
"revision_already_active", "charter revision is already active",
|
|
1993
|
+
)
|
|
1994
|
+
if status in {"superseded", "revoked"}:
|
|
1995
|
+
raise DriveGovernanceError(
|
|
1996
|
+
"revision_terminal", "terminal charter revision cannot reactivate",
|
|
1997
|
+
)
|
|
1998
|
+
if current is not None and revision.parent_revision_id != current.revision_id:
|
|
1999
|
+
raise DriveGovernanceError(
|
|
2000
|
+
"stale_charter_parent",
|
|
2001
|
+
"new charter parent is not the current active revision",
|
|
2002
|
+
)
|
|
2003
|
+
if current is None and revision.parent_revision_id:
|
|
2004
|
+
parent = conn.execute(
|
|
2005
|
+
"SELECT charter_key FROM charter_revisions WHERE revision_id=?",
|
|
2006
|
+
(revision.parent_revision_id,),
|
|
2007
|
+
).fetchone()
|
|
2008
|
+
if parent is None or parent["charter_key"] != revision.charter_key:
|
|
2009
|
+
raise DriveGovernanceError(
|
|
2010
|
+
"unknown_charter_parent", "charter parent is unknown",
|
|
2011
|
+
)
|
|
2012
|
+
for drive_id, _weight in revision.drive_weights:
|
|
2013
|
+
drive_row = conn.execute(
|
|
2014
|
+
"SELECT payload_json FROM drive_definitions WHERE drive_id=?",
|
|
2015
|
+
(drive_id,),
|
|
2016
|
+
).fetchone()
|
|
2017
|
+
if drive_row is None:
|
|
2018
|
+
raise DriveGovernanceError(
|
|
2019
|
+
"drive_unknown", f"charter drive is unknown: {drive_id}",
|
|
2020
|
+
)
|
|
2021
|
+
drive = DriveV1.from_payload(json.loads(drive_row["payload_json"]))
|
|
2022
|
+
drive_state = drive.state_at(now)
|
|
2023
|
+
if drive_state == "disabled":
|
|
2024
|
+
raise DriveGovernanceError(
|
|
2025
|
+
"drive_disabled", f"charter drive is disabled: {drive_id}",
|
|
2026
|
+
)
|
|
2027
|
+
if drive_state == "expired":
|
|
2028
|
+
raise DriveGovernanceError(
|
|
2029
|
+
"drive_expired", f"charter drive is expired: {drive_id}",
|
|
2030
|
+
)
|
|
2031
|
+
if not drive.scope.permits_child(revision.scope):
|
|
2032
|
+
# A charter projection may be as private as, or more
|
|
2033
|
+
# private than, its drive data. It must never make a
|
|
2034
|
+
# private drive's existence visible more broadly.
|
|
2035
|
+
raise DriveGovernanceError(
|
|
2036
|
+
"scope_broadening",
|
|
2037
|
+
"charter scope would broaden a referenced drive",
|
|
2038
|
+
)
|
|
2039
|
+
elif transition == "revoke":
|
|
2040
|
+
if current is None or current.revision_id != revision.revision_id:
|
|
2041
|
+
raise DriveGovernanceError(
|
|
2042
|
+
"revision_not_active", "only the active charter can be revoked",
|
|
2043
|
+
)
|
|
2044
|
+
else:
|
|
2045
|
+
raise DriveGovernanceError(
|
|
2046
|
+
"invalid_transition", "transition must be activate or revoke",
|
|
2047
|
+
)
|
|
2048
|
+
return current
|
|
2049
|
+
|
|
2050
|
+
def preflight_transition(
|
|
2051
|
+
self,
|
|
2052
|
+
revision_id: str,
|
|
2053
|
+
*,
|
|
2054
|
+
transition: str,
|
|
2055
|
+
now: Optional[datetime] = None,
|
|
2056
|
+
) -> tuple[CharterRevisionV1, Optional[CharterRevisionV1]]:
|
|
2057
|
+
revision = self.get_revision(revision_id)
|
|
2058
|
+
if revision is None:
|
|
2059
|
+
raise DriveGovernanceError(
|
|
2060
|
+
"revision_unknown", "charter revision is unknown",
|
|
2061
|
+
)
|
|
2062
|
+
normalized = str(transition or "").strip().lower()
|
|
2063
|
+
with self._lock:
|
|
2064
|
+
current = self._validate_transition_conn(
|
|
2065
|
+
self._conn, revision, normalized, _as_utc(now),
|
|
2066
|
+
)
|
|
2067
|
+
return revision, current
|
|
2068
|
+
|
|
2069
|
+
def transition_operation(self, operation_id: str) -> Optional[Dict[str, Any]]:
|
|
2070
|
+
operation = _operation_id(operation_id)
|
|
2071
|
+
with self._lock:
|
|
2072
|
+
row = self._conn.execute(
|
|
2073
|
+
"SELECT * FROM charter_transition_operations WHERE operation_id=?",
|
|
2074
|
+
(operation,),
|
|
2075
|
+
).fetchone()
|
|
2076
|
+
return dict(row) if row else None
|
|
2077
|
+
|
|
2078
|
+
def transition_authority_use(
|
|
2079
|
+
self, approval_request_id: str,
|
|
2080
|
+
) -> Optional[Dict[str, Any]]:
|
|
2081
|
+
"""Return validated application evidence for one approval request."""
|
|
2082
|
+
|
|
2083
|
+
request_id = _identifier(
|
|
2084
|
+
approval_request_id, "approval_request_id",
|
|
2085
|
+
)
|
|
2086
|
+
with self._lock:
|
|
2087
|
+
row = self._conn.execute(
|
|
2088
|
+
"SELECT u.approval_request_id,u.action_digest,u.operation_id,"
|
|
2089
|
+
"u.event_id,u.used_at,e.charter_key,e.revision_id,"
|
|
2090
|
+
"e.transition,e.decision_id,e.principal_id,e.payload_json,"
|
|
2091
|
+
"e.from_revision_id,e.authority_evidence,e.occurred_at "
|
|
2092
|
+
"FROM charter_authority_uses AS u "
|
|
2093
|
+
"JOIN charter_lifecycle_events AS e ON e.event_id=u.event_id "
|
|
2094
|
+
"WHERE u.approval_request_id=?",
|
|
2095
|
+
(request_id,),
|
|
2096
|
+
).fetchone()
|
|
2097
|
+
if row is None:
|
|
2098
|
+
return None
|
|
2099
|
+
payload = self._validate_lifecycle_event(row)
|
|
2100
|
+
result = dict(row)
|
|
2101
|
+
result.pop("payload_json", None)
|
|
2102
|
+
result["payload"] = payload
|
|
2103
|
+
return result
|
|
2104
|
+
|
|
2105
|
+
@staticmethod
|
|
2106
|
+
def _transition_digest(
|
|
2107
|
+
*,
|
|
2108
|
+
revision_id: str,
|
|
2109
|
+
transition: str,
|
|
2110
|
+
binding: ActionBinding,
|
|
2111
|
+
approval_request_id: str,
|
|
2112
|
+
) -> str:
|
|
2113
|
+
return _digest({
|
|
2114
|
+
"revision_id": revision_id,
|
|
2115
|
+
"transition": transition,
|
|
2116
|
+
"action_digest": binding.action_digest,
|
|
2117
|
+
"scope_digest": binding.scope_digest,
|
|
2118
|
+
"approval_request_id": approval_request_id,
|
|
2119
|
+
})
|
|
2120
|
+
|
|
2121
|
+
def _apply_transition(
|
|
2122
|
+
self,
|
|
2123
|
+
revision: CharterRevisionV1,
|
|
2124
|
+
*,
|
|
2125
|
+
transition: str,
|
|
2126
|
+
binding: ActionBinding,
|
|
2127
|
+
approval_request: Mapping[str, Any],
|
|
2128
|
+
operation_id: str,
|
|
2129
|
+
principal_id: str,
|
|
2130
|
+
require_no_active_charter: bool = False,
|
|
2131
|
+
now: Optional[datetime] = None,
|
|
2132
|
+
) -> Dict[str, Any]:
|
|
2133
|
+
"""Append an owner-ratified transition and atomically fence its use."""
|
|
2134
|
+
|
|
2135
|
+
observed = _as_utc(now)
|
|
2136
|
+
operation = _operation_id(operation_id)
|
|
2137
|
+
request_id = _identifier(
|
|
2138
|
+
approval_request.get("request_id"), "approval_request_id",
|
|
2139
|
+
)
|
|
2140
|
+
decision_id = _identifier(
|
|
2141
|
+
approval_request.get("decision_id"), "decision_id",
|
|
2142
|
+
)
|
|
2143
|
+
evidence = _text(
|
|
2144
|
+
approval_request.get("authority_evidence"),
|
|
2145
|
+
"authority_evidence", 256,
|
|
2146
|
+
)
|
|
2147
|
+
normalized = str(transition or "").strip().lower()
|
|
2148
|
+
transition_digest = self._transition_digest(
|
|
2149
|
+
revision_id=revision.revision_id,
|
|
2150
|
+
transition=normalized,
|
|
2151
|
+
binding=binding,
|
|
2152
|
+
approval_request_id=request_id,
|
|
2153
|
+
)
|
|
2154
|
+
with self._lock, self._conn:
|
|
2155
|
+
self._conn.execute("BEGIN IMMEDIATE")
|
|
2156
|
+
prior = self._conn.execute(
|
|
2157
|
+
"SELECT * FROM charter_transition_operations WHERE operation_id=?",
|
|
2158
|
+
(operation,),
|
|
2159
|
+
).fetchone()
|
|
2160
|
+
if prior is not None:
|
|
2161
|
+
if prior["transition_digest"] != transition_digest:
|
|
2162
|
+
raise DriveGovernanceError(
|
|
2163
|
+
"operation_replay_conflict",
|
|
2164
|
+
"ratification operation replay changed its binding",
|
|
2165
|
+
)
|
|
2166
|
+
projection = self._revision_projection_conn(
|
|
2167
|
+
self._conn, revision.revision_id, observed,
|
|
2168
|
+
)
|
|
2169
|
+
projection.update({
|
|
2170
|
+
"enabled": True,
|
|
2171
|
+
"status": f"charter_{normalized}_replayed",
|
|
2172
|
+
"event_id": prior["event_id"],
|
|
2173
|
+
"replayed": True,
|
|
2174
|
+
})
|
|
2175
|
+
return projection
|
|
2176
|
+
|
|
2177
|
+
used = self._conn.execute(
|
|
2178
|
+
"SELECT * FROM charter_authority_uses WHERE approval_request_id=?",
|
|
2179
|
+
(request_id,),
|
|
2180
|
+
).fetchone()
|
|
2181
|
+
if used is not None:
|
|
2182
|
+
raise DriveGovernanceError(
|
|
2183
|
+
"authority_replay",
|
|
2184
|
+
"approval request was already consumed by another transition",
|
|
2185
|
+
)
|
|
2186
|
+
|
|
2187
|
+
current = self._validate_transition_conn(
|
|
2188
|
+
self._conn, revision, normalized, observed,
|
|
2189
|
+
)
|
|
2190
|
+
if (
|
|
2191
|
+
require_no_active_charter
|
|
2192
|
+
and self._active_revisions_conn(self._conn, observed)
|
|
2193
|
+
):
|
|
2194
|
+
raise DriveGovernanceError(
|
|
2195
|
+
"bootstrap_transition_held",
|
|
2196
|
+
"bootstrap may activate only the first global root charter",
|
|
2197
|
+
)
|
|
2198
|
+
event_payload = {
|
|
2199
|
+
"schema": "CharterLifecycleEventV1", "version": 1,
|
|
2200
|
+
"charter_key": revision.charter_key,
|
|
2201
|
+
"revision_id": revision.revision_id,
|
|
2202
|
+
"transition": normalized,
|
|
2203
|
+
"from_revision_id": (
|
|
2204
|
+
current.revision_id if current is not None else None
|
|
2205
|
+
),
|
|
2206
|
+
"action_digest": binding.action_digest,
|
|
2207
|
+
"approval_request_id": request_id,
|
|
2208
|
+
"decision_id": decision_id,
|
|
2209
|
+
"principal_id": principal_id,
|
|
2210
|
+
"authority_evidence": evidence,
|
|
2211
|
+
"occurred_at": _iso(observed),
|
|
2212
|
+
}
|
|
2213
|
+
event_id = f"charter-event:{_digest(event_payload)[:24]}"
|
|
2214
|
+
|
|
2215
|
+
if normalized == "activate" and current is not None:
|
|
2216
|
+
supersede_payload = {
|
|
2217
|
+
"schema": "CharterLifecycleEventV1", "version": 1,
|
|
2218
|
+
"charter_key": revision.charter_key,
|
|
2219
|
+
"revision_id": current.revision_id,
|
|
2220
|
+
"transition": "supersede",
|
|
2221
|
+
"from_revision_id": revision.revision_id,
|
|
2222
|
+
"action_digest": binding.action_digest,
|
|
2223
|
+
"approval_request_id": request_id,
|
|
2224
|
+
"decision_id": decision_id,
|
|
2225
|
+
"principal_id": principal_id,
|
|
2226
|
+
"authority_evidence": evidence,
|
|
2227
|
+
"occurred_at": _iso(observed),
|
|
2228
|
+
}
|
|
2229
|
+
supersede_id = (
|
|
2230
|
+
f"charter-event:{_digest(supersede_payload)[:24]}"
|
|
2231
|
+
)
|
|
2232
|
+
self._conn.execute(
|
|
2233
|
+
"INSERT INTO charter_lifecycle_events "
|
|
2234
|
+
"(event_id,charter_key,revision_id,transition,from_revision_id,"
|
|
2235
|
+
"action_digest,approval_request_id,decision_id,principal_id,"
|
|
2236
|
+
"authority_evidence,occurred_at,payload_json) "
|
|
2237
|
+
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",
|
|
2238
|
+
(
|
|
2239
|
+
supersede_id, revision.charter_key, current.revision_id,
|
|
2240
|
+
"supersede", revision.revision_id, binding.action_digest,
|
|
2241
|
+
request_id, decision_id, principal_id, evidence,
|
|
2242
|
+
_iso(observed), _canonical(supersede_payload),
|
|
2243
|
+
),
|
|
2244
|
+
)
|
|
2245
|
+
|
|
2246
|
+
self._conn.execute(
|
|
2247
|
+
"INSERT INTO charter_lifecycle_events "
|
|
2248
|
+
"(event_id,charter_key,revision_id,transition,from_revision_id,"
|
|
2249
|
+
"action_digest,approval_request_id,decision_id,principal_id,"
|
|
2250
|
+
"authority_evidence,occurred_at,payload_json) "
|
|
2251
|
+
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",
|
|
2252
|
+
(
|
|
2253
|
+
event_id, revision.charter_key, revision.revision_id,
|
|
2254
|
+
normalized,
|
|
2255
|
+
current.revision_id if current is not None else None,
|
|
2256
|
+
binding.action_digest, request_id, decision_id, principal_id,
|
|
2257
|
+
evidence, _iso(observed), _canonical(event_payload),
|
|
2258
|
+
),
|
|
2259
|
+
)
|
|
2260
|
+
self._conn.execute(
|
|
2261
|
+
"INSERT INTO charter_authority_uses "
|
|
2262
|
+
"(approval_request_id,action_digest,operation_id,event_id,used_at) "
|
|
2263
|
+
"VALUES (?,?,?,?,?)",
|
|
2264
|
+
(
|
|
2265
|
+
request_id, binding.action_digest, operation, event_id,
|
|
2266
|
+
_iso(observed),
|
|
2267
|
+
),
|
|
2268
|
+
)
|
|
2269
|
+
self._conn.execute(
|
|
2270
|
+
"INSERT INTO charter_transition_operations "
|
|
2271
|
+
"(operation_id,transition_digest,revision_id,transition,"
|
|
2272
|
+
"approval_request_id,event_id,created_at) VALUES (?,?,?,?,?,?,?)",
|
|
2273
|
+
(
|
|
2274
|
+
operation, transition_digest, revision.revision_id,
|
|
2275
|
+
normalized, request_id, event_id, _iso(observed),
|
|
2276
|
+
),
|
|
2277
|
+
)
|
|
2278
|
+
projection = self._revision_projection_conn(
|
|
2279
|
+
self._conn, revision.revision_id, observed,
|
|
2280
|
+
)
|
|
2281
|
+
projection.update({
|
|
2282
|
+
"enabled": True,
|
|
2283
|
+
"status": f"charter_{normalized}d",
|
|
2284
|
+
"event_id": event_id,
|
|
2285
|
+
"replayed": False,
|
|
2286
|
+
})
|
|
2287
|
+
return projection
|
|
2288
|
+
|
|
2289
|
+
def lifecycle_events(self, charter_key: str = "default") -> list[Dict[str, Any]]:
|
|
2290
|
+
with self._lock:
|
|
2291
|
+
rows = self._conn.execute(
|
|
2292
|
+
"SELECT * FROM charter_lifecycle_events WHERE charter_key=? "
|
|
2293
|
+
"ORDER BY event_seq", (str(charter_key),),
|
|
2294
|
+
).fetchall()
|
|
2295
|
+
result = []
|
|
2296
|
+
for row in rows:
|
|
2297
|
+
item = dict(row)
|
|
2298
|
+
item["payload"] = self._validate_lifecycle_event(row)
|
|
2299
|
+
item.pop("payload_json")
|
|
2300
|
+
result.append(item)
|
|
2301
|
+
return result
|
|
2302
|
+
|
|
2303
|
+
def observer_projection(
|
|
2304
|
+
self,
|
|
2305
|
+
*,
|
|
2306
|
+
viewer_person_id: str,
|
|
2307
|
+
owner_person_id: str,
|
|
2308
|
+
audiences: set[str] | frozenset[str] = frozenset(),
|
|
2309
|
+
charter_key: str = "default",
|
|
2310
|
+
now: Optional[datetime] = None,
|
|
2311
|
+
signal_limit: int = 100,
|
|
2312
|
+
) -> Dict[str, Any]:
|
|
2313
|
+
observed = _as_utc(now)
|
|
2314
|
+
revisions = self.list_revision_projections(charter_key, now=observed)
|
|
2315
|
+
visible_revisions = [
|
|
2316
|
+
item for item in revisions
|
|
2317
|
+
if ScopeV1(**dict(item["scope"])).visible_to(
|
|
2318
|
+
viewer_person_id=viewer_person_id,
|
|
2319
|
+
owner_person_id=owner_person_id,
|
|
2320
|
+
audiences=audiences,
|
|
2321
|
+
)
|
|
2322
|
+
]
|
|
2323
|
+
drives = [
|
|
2324
|
+
item for item in self.list_drives()
|
|
2325
|
+
if item.scope.visible_to(
|
|
2326
|
+
viewer_person_id=viewer_person_id,
|
|
2327
|
+
owner_person_id=owner_person_id,
|
|
2328
|
+
audiences=audiences,
|
|
2329
|
+
)
|
|
2330
|
+
]
|
|
2331
|
+
bounded_limit = max(1, min(int(signal_limit), 500))
|
|
2332
|
+
with self._lock:
|
|
2333
|
+
rows = self._conn.execute(
|
|
2334
|
+
"SELECT payload_json FROM drive_signals "
|
|
2335
|
+
"ORDER BY observed_at DESC,signal_seq DESC LIMIT ?",
|
|
2336
|
+
(bounded_limit,),
|
|
2337
|
+
).fetchall()
|
|
2338
|
+
signals = [
|
|
2339
|
+
item for item in (
|
|
2340
|
+
DriveSignalV1.from_payload(json.loads(row["payload_json"]))
|
|
2341
|
+
for row in rows
|
|
2342
|
+
)
|
|
2343
|
+
if item.scope.visible_to(
|
|
2344
|
+
viewer_person_id=viewer_person_id,
|
|
2345
|
+
owner_person_id=owner_person_id,
|
|
2346
|
+
audiences=audiences,
|
|
2347
|
+
)
|
|
2348
|
+
]
|
|
2349
|
+
active = self.active_revision(charter_key, now=observed)
|
|
2350
|
+
active_visible = bool(
|
|
2351
|
+
active
|
|
2352
|
+
and active.scope.visible_to(
|
|
2353
|
+
viewer_person_id=viewer_person_id,
|
|
2354
|
+
owner_person_id=owner_person_id,
|
|
2355
|
+
audiences=audiences,
|
|
2356
|
+
)
|
|
2357
|
+
)
|
|
2358
|
+
return {
|
|
2359
|
+
"schema": "DriveGovernanceObserverV1", "version": 1,
|
|
2360
|
+
"charter_key": charter_key,
|
|
2361
|
+
"active_charter_revision_id": (
|
|
2362
|
+
active.revision_id if active_visible and active else None
|
|
2363
|
+
),
|
|
2364
|
+
"charter_revisions": visible_revisions,
|
|
2365
|
+
"drives": [
|
|
2366
|
+
{**item.payload(), "effective_state": item.state_at(observed)}
|
|
2367
|
+
for item in drives
|
|
2368
|
+
],
|
|
2369
|
+
"signals": [
|
|
2370
|
+
{**item.payload(), "effective_state": item.state_at(observed)}
|
|
2371
|
+
for item in signals
|
|
2372
|
+
],
|
|
2373
|
+
"generated_at": _iso(observed),
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
|
|
2377
|
+
class DriveGovernance:
|
|
2378
|
+
"""Mode-gated service reusing existing approvals for charter authority."""
|
|
2379
|
+
|
|
2380
|
+
def __init__(
|
|
2381
|
+
self,
|
|
2382
|
+
store: Optional[DriveGovernanceStore],
|
|
2383
|
+
approval_store: Optional[ApprovalAuthorityStore],
|
|
2384
|
+
*,
|
|
2385
|
+
mode: Optional[str] = None,
|
|
2386
|
+
) -> None:
|
|
2387
|
+
self.store = store
|
|
2388
|
+
self.approval_store = approval_store
|
|
2389
|
+
self.mode = _normalized_mode(mode)
|
|
2390
|
+
if self.mode != "off" and self.store is None:
|
|
2391
|
+
raise DriveGovernanceError(
|
|
2392
|
+
"store_required", "drive governance store is required",
|
|
2393
|
+
)
|
|
2394
|
+
|
|
2395
|
+
@classmethod
|
|
2396
|
+
def lazy(
|
|
2397
|
+
cls,
|
|
2398
|
+
db_path: str | os.PathLike[str],
|
|
2399
|
+
*,
|
|
2400
|
+
approval_db_path: Optional[str | os.PathLike[str]] = None,
|
|
2401
|
+
mode: Optional[str] = None,
|
|
2402
|
+
) -> "DriveGovernance":
|
|
2403
|
+
selected = _normalized_mode(mode)
|
|
2404
|
+
if selected == "off":
|
|
2405
|
+
# Default-off means no database, directory, or approval state is
|
|
2406
|
+
# created merely because a host imports or constructs the module.
|
|
2407
|
+
return cls(None, None, mode="off")
|
|
2408
|
+
store = DriveGovernanceStore(db_path)
|
|
2409
|
+
approval_store = ApprovalAuthorityStore(approval_db_path)
|
|
2410
|
+
return cls(store, approval_store, mode=selected)
|
|
2411
|
+
|
|
2412
|
+
def _store(self) -> DriveGovernanceStore:
|
|
2413
|
+
if self.store is None:
|
|
2414
|
+
raise DriveGovernanceError(
|
|
2415
|
+
"store_required", "drive governance store is unavailable",
|
|
2416
|
+
)
|
|
2417
|
+
return self.store
|
|
2418
|
+
|
|
2419
|
+
def register_drive(
|
|
2420
|
+
self,
|
|
2421
|
+
drive: DriveV1,
|
|
2422
|
+
*,
|
|
2423
|
+
operation_id: str,
|
|
2424
|
+
now: Optional[datetime] = None,
|
|
2425
|
+
) -> Dict[str, Any]:
|
|
2426
|
+
if self.mode == "off":
|
|
2427
|
+
return {"enabled": False, "status": "off"}
|
|
2428
|
+
return self._store().register_drive(
|
|
2429
|
+
drive, operation_id=operation_id, now=now,
|
|
2430
|
+
)
|
|
2431
|
+
|
|
2432
|
+
def record_signal(
|
|
2433
|
+
self,
|
|
2434
|
+
signal: DriveSignalV1,
|
|
2435
|
+
*,
|
|
2436
|
+
operation_id: str,
|
|
2437
|
+
now: Optional[datetime] = None,
|
|
2438
|
+
) -> Dict[str, Any]:
|
|
2439
|
+
if self.mode == "off":
|
|
2440
|
+
return {"enabled": False, "status": "off"}
|
|
2441
|
+
if self.mode == "bootstrap":
|
|
2442
|
+
raise DriveGovernanceError(
|
|
2443
|
+
"bootstrap_operation_held",
|
|
2444
|
+
"drive signals are held during initial charter bootstrap",
|
|
2445
|
+
)
|
|
2446
|
+
return self._store().record_signal(
|
|
2447
|
+
signal, operation_id=operation_id, now=now,
|
|
2448
|
+
)
|
|
2449
|
+
|
|
2450
|
+
def propose_charter(
|
|
2451
|
+
self,
|
|
2452
|
+
revision: CharterRevisionV1,
|
|
2453
|
+
*,
|
|
2454
|
+
operation_id: str,
|
|
2455
|
+
now: Optional[datetime] = None,
|
|
2456
|
+
) -> Dict[str, Any]:
|
|
2457
|
+
if self.mode == "off":
|
|
2458
|
+
return {"enabled": False, "status": "off"}
|
|
2459
|
+
result = self._store().propose_revision(
|
|
2460
|
+
revision, operation_id=operation_id, now=now,
|
|
2461
|
+
)
|
|
2462
|
+
result.setdefault("status", (
|
|
2463
|
+
"charter_proposal_replayed" if result.get("replayed")
|
|
2464
|
+
else "charter_proposed"
|
|
2465
|
+
))
|
|
2466
|
+
result["effect_executed"] = False
|
|
2467
|
+
return result
|
|
2468
|
+
|
|
2469
|
+
def ensure_transition_request(
|
|
2470
|
+
self,
|
|
2471
|
+
revision_id: str,
|
|
2472
|
+
*,
|
|
2473
|
+
transition: str,
|
|
2474
|
+
ttl_seconds: int = 3600,
|
|
2475
|
+
now: Optional[datetime] = None,
|
|
2476
|
+
) -> Dict[str, Any]:
|
|
2477
|
+
if self.mode == "off":
|
|
2478
|
+
return {"enabled": False, "status": "off"}
|
|
2479
|
+
observed = _as_utc(now)
|
|
2480
|
+
store = self._store()
|
|
2481
|
+
revision, current = store.preflight_transition(
|
|
2482
|
+
revision_id, transition=transition, now=observed,
|
|
2483
|
+
)
|
|
2484
|
+
normalized = str(transition).strip().lower()
|
|
2485
|
+
if self.mode == "bootstrap" and (
|
|
2486
|
+
normalized != "activate"
|
|
2487
|
+
or current is not None
|
|
2488
|
+
or revision.parent_revision_id is not None
|
|
2489
|
+
or store.active_revisions(now=observed)
|
|
2490
|
+
):
|
|
2491
|
+
raise DriveGovernanceError(
|
|
2492
|
+
"bootstrap_transition_held",
|
|
2493
|
+
"bootstrap may activate only the first global root charter",
|
|
2494
|
+
)
|
|
2495
|
+
binding = charter_transition_binding(
|
|
2496
|
+
revision,
|
|
2497
|
+
transition=normalized,
|
|
2498
|
+
expected_active_revision_id=(
|
|
2499
|
+
current.revision_id if current is not None else None
|
|
2500
|
+
),
|
|
2501
|
+
)
|
|
2502
|
+
if self.mode == "shadow":
|
|
2503
|
+
return {
|
|
2504
|
+
"enabled": True,
|
|
2505
|
+
"status": "shadow_transition_candidate",
|
|
2506
|
+
"transition": normalized,
|
|
2507
|
+
"revision_id": revision.revision_id,
|
|
2508
|
+
"action_digest": binding.action_digest,
|
|
2509
|
+
"scope_digest": binding.scope_digest,
|
|
2510
|
+
"expected_active_revision_id": (
|
|
2511
|
+
current.revision_id if current else None
|
|
2512
|
+
),
|
|
2513
|
+
"effect_executed": False,
|
|
2514
|
+
}
|
|
2515
|
+
if self.approval_store is None:
|
|
2516
|
+
raise DriveGovernanceError(
|
|
2517
|
+
"approval_store_required", "approval authority store is unavailable",
|
|
2518
|
+
)
|
|
2519
|
+
ttl = _bounded_int(
|
|
2520
|
+
ttl_seconds, "transition request ttl_seconds",
|
|
2521
|
+
minimum=60, maximum=24 * 60 * 60,
|
|
2522
|
+
)
|
|
2523
|
+
job_id = f"charter-transition:{binding.action_digest[:24]}"
|
|
2524
|
+
subject = charter_transition_subject(
|
|
2525
|
+
revision, transition=normalized,
|
|
2526
|
+
)
|
|
2527
|
+
presentation = charter_transition_presentation(
|
|
2528
|
+
revision, transition=normalized, binding=binding,
|
|
2529
|
+
)
|
|
2530
|
+
request = self.approval_store.ensure_request(
|
|
2531
|
+
job_id=job_id,
|
|
2532
|
+
binding=binding,
|
|
2533
|
+
ttl_seconds=ttl,
|
|
2534
|
+
presentation=presentation,
|
|
2535
|
+
subject=subject,
|
|
2536
|
+
now=observed,
|
|
2537
|
+
)
|
|
2538
|
+
return {
|
|
2539
|
+
**request,
|
|
2540
|
+
"enabled": True,
|
|
2541
|
+
"transition": normalized,
|
|
2542
|
+
"revision_id": revision.revision_id,
|
|
2543
|
+
"effect_executed": False,
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
def _validated_transition_approval(
|
|
2547
|
+
self,
|
|
2548
|
+
request_id: str,
|
|
2549
|
+
*,
|
|
2550
|
+
now: Optional[datetime] = None,
|
|
2551
|
+
) -> tuple[Dict[str, Any], CharterRevisionV1, str, bool, Optional[Dict[str, Any]]]:
|
|
2552
|
+
if self.approval_store is None:
|
|
2553
|
+
raise DriveGovernanceError(
|
|
2554
|
+
"approval_store_required",
|
|
2555
|
+
"approval authority store is unavailable",
|
|
2556
|
+
)
|
|
2557
|
+
observed = _as_utc(now)
|
|
2558
|
+
request = self.approval_store.get_request(request_id, now=observed)
|
|
2559
|
+
if request is None:
|
|
2560
|
+
raise DriveGovernanceError(
|
|
2561
|
+
"approval_request_missing", "approval request was not found",
|
|
2562
|
+
)
|
|
2563
|
+
subject = request.get("subject")
|
|
2564
|
+
if not isinstance(subject, Mapping) or subject.get("kind") !=\
|
|
2565
|
+
"charter_transition":
|
|
2566
|
+
raise DriveGovernanceError(
|
|
2567
|
+
"approval_subject_unavailable",
|
|
2568
|
+
"approval request is not a charter transition subject",
|
|
2569
|
+
)
|
|
2570
|
+
normalized = str(subject.get("action") or "").strip().lower()
|
|
2571
|
+
if normalized not in _TRANSITIONS:
|
|
2572
|
+
raise DriveGovernanceError(
|
|
2573
|
+
"approval_subject_invalid",
|
|
2574
|
+
"charter transition approval action is invalid",
|
|
2575
|
+
)
|
|
2576
|
+
revision = self._store().get_revision(str(subject.get("subject_id") or ""))
|
|
2577
|
+
if revision is None or subject.get("revision") != revision.content_digest:
|
|
2578
|
+
raise DriveGovernanceError(
|
|
2579
|
+
"approval_subject_unavailable",
|
|
2580
|
+
"charter transition approval subject is orphaned",
|
|
2581
|
+
)
|
|
2582
|
+
expected_subject = charter_transition_subject(
|
|
2583
|
+
revision, transition=normalized,
|
|
2584
|
+
).payload()
|
|
2585
|
+
if dict(subject) != expected_subject:
|
|
2586
|
+
raise DriveGovernanceError(
|
|
2587
|
+
"approval_subject_invalid",
|
|
2588
|
+
"charter transition approval subject binding is invalid",
|
|
2589
|
+
)
|
|
2590
|
+
|
|
2591
|
+
scope = request.get("scope")
|
|
2592
|
+
constraints = scope.get("constraints") if isinstance(scope, Mapping) else None
|
|
2593
|
+
expected_constraints = {
|
|
2594
|
+
"charter_key_digest": _digest(revision.charter_key),
|
|
2595
|
+
"revision_id_digest": _digest(revision.revision_id),
|
|
2596
|
+
"content_digest": revision.content_digest,
|
|
2597
|
+
}
|
|
2598
|
+
valid_scope = bool(
|
|
2599
|
+
isinstance(scope, Mapping)
|
|
2600
|
+
and set(scope) == {
|
|
2601
|
+
"version", "job_type", "action_name", "risk", "constraints",
|
|
2602
|
+
}
|
|
2603
|
+
and scope.get("version") == 1
|
|
2604
|
+
and scope.get("job_type") == "charter_governance"
|
|
2605
|
+
and scope.get("action_name") == f"charter_revision_{normalized}"
|
|
2606
|
+
and scope.get("risk") == "authority_mutation"
|
|
2607
|
+
and isinstance(constraints, Mapping)
|
|
2608
|
+
and set(constraints) == {
|
|
2609
|
+
*expected_constraints,
|
|
2610
|
+
"expected_active_revision_digest",
|
|
2611
|
+
}
|
|
2612
|
+
and all(
|
|
2613
|
+
constraints.get(key) == value
|
|
2614
|
+
for key, value in expected_constraints.items()
|
|
2615
|
+
)
|
|
2616
|
+
and re.fullmatch(
|
|
2617
|
+
r"[0-9a-f]{64}",
|
|
2618
|
+
str(constraints.get("expected_active_revision_digest") or ""),
|
|
2619
|
+
) is not None
|
|
2620
|
+
and _digest(scope) == request.get("scope_digest")
|
|
2621
|
+
)
|
|
2622
|
+
if not valid_scope:
|
|
2623
|
+
raise DriveGovernanceError(
|
|
2624
|
+
"approval_binding_invalid",
|
|
2625
|
+
"charter transition approval scope is invalid",
|
|
2626
|
+
)
|
|
2627
|
+
expected_presentation = charter_transition_presentation(
|
|
2628
|
+
revision,
|
|
2629
|
+
transition=normalized,
|
|
2630
|
+
binding=ActionBinding(
|
|
2631
|
+
action_digest=str(request.get("action_digest") or ""),
|
|
2632
|
+
scope=dict(scope),
|
|
2633
|
+
scope_digest=str(request.get("scope_digest") or ""),
|
|
2634
|
+
),
|
|
2635
|
+
)
|
|
2636
|
+
if request.get("presentation") != expected_presentation:
|
|
2637
|
+
raise DriveGovernanceError(
|
|
2638
|
+
"approval_presentation_invalid",
|
|
2639
|
+
"charter transition approval presentation is invalid",
|
|
2640
|
+
)
|
|
2641
|
+
if request.get("job_id") != (
|
|
2642
|
+
f"charter-transition:{str(request.get('action_digest') or '')[:24]}"
|
|
2643
|
+
):
|
|
2644
|
+
raise DriveGovernanceError(
|
|
2645
|
+
"approval_binding_invalid",
|
|
2646
|
+
"charter transition approval identifier is invalid",
|
|
2647
|
+
)
|
|
2648
|
+
if request.get("status") in {"approved", "rejected"}:
|
|
2649
|
+
decided_by = str(request.get("decided_by") or "").strip()
|
|
2650
|
+
evidence = str(request.get("authority_evidence") or "").strip()
|
|
2651
|
+
evidence_prefix = f"scoped_principal:{decided_by}:"
|
|
2652
|
+
credential_id = (
|
|
2653
|
+
evidence[len(evidence_prefix):]
|
|
2654
|
+
if decided_by and evidence.startswith(evidence_prefix)
|
|
2655
|
+
else ""
|
|
2656
|
+
)
|
|
2657
|
+
try:
|
|
2658
|
+
_identifier(decided_by, "decided_by")
|
|
2659
|
+
_identifier(credential_id, "decision_credential_id")
|
|
2660
|
+
except DriveGovernanceError as exc:
|
|
2661
|
+
raise DriveGovernanceError(
|
|
2662
|
+
"approval_authority_integrity_error",
|
|
2663
|
+
"charter transition decision authority is invalid",
|
|
2664
|
+
) from exc
|
|
2665
|
+
|
|
2666
|
+
use = self._store().transition_authority_use(request["request_id"])
|
|
2667
|
+
if use is not None:
|
|
2668
|
+
from_revision_id = use.get("from_revision_id")
|
|
2669
|
+
applied_binding = charter_transition_binding(
|
|
2670
|
+
revision,
|
|
2671
|
+
transition=normalized,
|
|
2672
|
+
expected_active_revision_id=from_revision_id,
|
|
2673
|
+
)
|
|
2674
|
+
if (
|
|
2675
|
+
use.get("revision_id") != revision.revision_id
|
|
2676
|
+
or use.get("transition") != normalized
|
|
2677
|
+
or use.get("action_digest") != request.get("action_digest")
|
|
2678
|
+
or applied_binding.action_digest != request.get("action_digest")
|
|
2679
|
+
or applied_binding.scope_digest != request.get("scope_digest")
|
|
2680
|
+
):
|
|
2681
|
+
raise DriveGovernanceError(
|
|
2682
|
+
"approval_application_integrity_error",
|
|
2683
|
+
"charter transition application does not match its approval",
|
|
2684
|
+
)
|
|
2685
|
+
current_binding_matches = False
|
|
2686
|
+
else:
|
|
2687
|
+
store = self._store()
|
|
2688
|
+
current = store.active_revision(
|
|
2689
|
+
revision.charter_key, now=observed,
|
|
2690
|
+
)
|
|
2691
|
+
current_binding = charter_transition_binding(
|
|
2692
|
+
revision,
|
|
2693
|
+
transition=normalized,
|
|
2694
|
+
expected_active_revision_id=(
|
|
2695
|
+
current.revision_id if current is not None else None
|
|
2696
|
+
),
|
|
2697
|
+
)
|
|
2698
|
+
current_binding_matches = bool(
|
|
2699
|
+
current_binding.action_digest == request.get("action_digest")
|
|
2700
|
+
and current_binding.scope_digest == request.get("scope_digest")
|
|
2701
|
+
and not (
|
|
2702
|
+
self.mode == "bootstrap"
|
|
2703
|
+
and store.active_revisions(now=observed)
|
|
2704
|
+
)
|
|
2705
|
+
)
|
|
2706
|
+
return request, revision, normalized, current_binding_matches, use
|
|
2707
|
+
|
|
2708
|
+
def transition_approval_projection(
|
|
2709
|
+
self,
|
|
2710
|
+
request_id: str,
|
|
2711
|
+
*,
|
|
2712
|
+
now: Optional[datetime] = None,
|
|
2713
|
+
) -> Dict[str, Any]:
|
|
2714
|
+
"""Return one redacted typed approval, never a raw authority row."""
|
|
2715
|
+
|
|
2716
|
+
observed = _as_utc(now)
|
|
2717
|
+
request, revision, transition, binding_current, use =\
|
|
2718
|
+
self._validated_transition_approval(request_id, now=observed)
|
|
2719
|
+
if use is not None:
|
|
2720
|
+
status = "approved_applied"
|
|
2721
|
+
application = {
|
|
2722
|
+
"status": "applied",
|
|
2723
|
+
"event_id": use["event_id"],
|
|
2724
|
+
"operation_id": use["operation_id"],
|
|
2725
|
+
"applied_at": use["used_at"],
|
|
2726
|
+
}
|
|
2727
|
+
elif request["status"] == "approved" and binding_current:
|
|
2728
|
+
status = "approved_unapplied"
|
|
2729
|
+
application = {
|
|
2730
|
+
"status": "recovery_required",
|
|
2731
|
+
"event_id": None,
|
|
2732
|
+
"operation_id": None,
|
|
2733
|
+
"applied_at": None,
|
|
2734
|
+
}
|
|
2735
|
+
elif request["status"] == "approved":
|
|
2736
|
+
status = "approved_stale"
|
|
2737
|
+
application = {
|
|
2738
|
+
"status": "new_request_required",
|
|
2739
|
+
"event_id": None,
|
|
2740
|
+
"operation_id": None,
|
|
2741
|
+
"applied_at": None,
|
|
2742
|
+
}
|
|
2743
|
+
elif request["status"] == "rejected":
|
|
2744
|
+
status = "rejected"
|
|
2745
|
+
application = {
|
|
2746
|
+
"status": "not_applicable",
|
|
2747
|
+
"event_id": None,
|
|
2748
|
+
"operation_id": None,
|
|
2749
|
+
"applied_at": None,
|
|
2750
|
+
}
|
|
2751
|
+
elif request["status"] == "pending" and not binding_current:
|
|
2752
|
+
status = "stale_pending"
|
|
2753
|
+
application = {
|
|
2754
|
+
"status": "binding_changed",
|
|
2755
|
+
"event_id": None,
|
|
2756
|
+
"operation_id": None,
|
|
2757
|
+
"applied_at": None,
|
|
2758
|
+
}
|
|
2759
|
+
else:
|
|
2760
|
+
status = request["status"]
|
|
2761
|
+
application = {
|
|
2762
|
+
"status": "awaiting_decision",
|
|
2763
|
+
"event_id": None,
|
|
2764
|
+
"operation_id": None,
|
|
2765
|
+
"applied_at": None,
|
|
2766
|
+
}
|
|
2767
|
+
return {
|
|
2768
|
+
"schema": CHARTER_TRANSITION_APPROVAL_PROJECTION_SCHEMA,
|
|
2769
|
+
"version": 1,
|
|
2770
|
+
"subject_kind": "charter_transition",
|
|
2771
|
+
"subject_id": revision.revision_id,
|
|
2772
|
+
"subject_revision": revision.content_digest,
|
|
2773
|
+
"subject_action": transition,
|
|
2774
|
+
"subject_digest": request["subject_digest"],
|
|
2775
|
+
"request_id": request["request_id"],
|
|
2776
|
+
"request_digest": request["request_digest"],
|
|
2777
|
+
"request_digest_version": request["request_digest_version"],
|
|
2778
|
+
"action_digest": request["action_digest"],
|
|
2779
|
+
"scope_digest": request["scope_digest"],
|
|
2780
|
+
"binding_digest": request["binding_digest"],
|
|
2781
|
+
"presentation": request["presentation"],
|
|
2782
|
+
"presentation_digest": request["presentation_digest"],
|
|
2783
|
+
"status": status,
|
|
2784
|
+
"authority_status": request["status"],
|
|
2785
|
+
"binding_current": binding_current,
|
|
2786
|
+
"created_at": request["created_at"],
|
|
2787
|
+
"expires_at": request["expires_at"],
|
|
2788
|
+
"decision": request.get("decision"),
|
|
2789
|
+
"decision_id": request.get("decision_id"),
|
|
2790
|
+
"decided_at": request.get("decided_at"),
|
|
2791
|
+
"decided_by": request.get("decided_by"),
|
|
2792
|
+
"authority_evidence": request.get("authority_evidence"),
|
|
2793
|
+
"application": application,
|
|
2794
|
+
"observed_at": _iso(observed),
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2797
|
+
def list_transition_approval_projections(
|
|
2798
|
+
self,
|
|
2799
|
+
*,
|
|
2800
|
+
status: Optional[str] = None,
|
|
2801
|
+
limit: int = 100,
|
|
2802
|
+
now: Optional[datetime] = None,
|
|
2803
|
+
) -> list[Dict[str, Any]]:
|
|
2804
|
+
return self.transition_approval_inventory(
|
|
2805
|
+
status=status, limit=limit, now=now,
|
|
2806
|
+
)["requests"]
|
|
2807
|
+
|
|
2808
|
+
def transition_approval_inventory(
|
|
2809
|
+
self,
|
|
2810
|
+
*,
|
|
2811
|
+
status: Optional[str] = None,
|
|
2812
|
+
limit: int = 100,
|
|
2813
|
+
now: Optional[datetime] = None,
|
|
2814
|
+
) -> Dict[str, Any]:
|
|
2815
|
+
"""Return valid projections plus a bounded count of hidden invalids."""
|
|
2816
|
+
|
|
2817
|
+
if self.approval_store is None:
|
|
2818
|
+
return {
|
|
2819
|
+
"requests": [], "invalid_hidden_count": 0,
|
|
2820
|
+
"candidate_count": 0, "complete": True,
|
|
2821
|
+
}
|
|
2822
|
+
observed = _as_utc(now)
|
|
2823
|
+
inspection = self.approval_store.inspect_subject_requests(
|
|
2824
|
+
subject_kind="charter_transition",
|
|
2825
|
+
job_prefix="charter-transition:",
|
|
2826
|
+
limit=500,
|
|
2827
|
+
now=observed,
|
|
2828
|
+
)
|
|
2829
|
+
candidates = inspection["requests"]
|
|
2830
|
+
invalid_hidden_count = int(inspection["invalid_hidden_count"])
|
|
2831
|
+
projections: list[Dict[str, Any]] = []
|
|
2832
|
+
bounded_limit = max(1, min(int(limit), 500))
|
|
2833
|
+
projection_overflow = False
|
|
2834
|
+
for candidate in candidates:
|
|
2835
|
+
try:
|
|
2836
|
+
projection = self.transition_approval_projection(
|
|
2837
|
+
candidate["request_id"], now=observed,
|
|
2838
|
+
)
|
|
2839
|
+
except DriveGovernanceError as exc:
|
|
2840
|
+
if exc.code in {
|
|
2841
|
+
"approval_subject_unavailable", "approval_subject_invalid",
|
|
2842
|
+
"approval_binding_invalid", "approval_presentation_invalid",
|
|
2843
|
+
"approval_application_integrity_error",
|
|
2844
|
+
"approval_authority_integrity_error",
|
|
2845
|
+
}:
|
|
2846
|
+
invalid_hidden_count += 1
|
|
2847
|
+
continue
|
|
2848
|
+
raise
|
|
2849
|
+
if status and projection["status"] != status:
|
|
2850
|
+
continue
|
|
2851
|
+
if len(projections) < bounded_limit:
|
|
2852
|
+
projections.append(projection)
|
|
2853
|
+
else:
|
|
2854
|
+
projection_overflow = True
|
|
2855
|
+
return {
|
|
2856
|
+
"requests": projections,
|
|
2857
|
+
"invalid_hidden_count": invalid_hidden_count,
|
|
2858
|
+
"candidate_count": int(inspection["candidate_count"]),
|
|
2859
|
+
"complete": bool(
|
|
2860
|
+
inspection["complete"] and not projection_overflow
|
|
2861
|
+
),
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2864
|
+
@staticmethod
|
|
2865
|
+
def _validate_transition_decision_authority(authority: Any) -> tuple[str, str]:
|
|
2866
|
+
allowed = bool(
|
|
2867
|
+
authority is not None
|
|
2868
|
+
and getattr(authority, "authenticated", False)
|
|
2869
|
+
and not getattr(authority, "legacy", False)
|
|
2870
|
+
and not getattr(authority, "anonymous", False)
|
|
2871
|
+
and callable(getattr(authority, "has_scope", None))
|
|
2872
|
+
and authority.has_scope("charter:approval-decide")
|
|
2873
|
+
and "owner" in set(getattr(authority, "audiences", ()))
|
|
2874
|
+
and str(getattr(authority, "principal_id", "")).strip()
|
|
2875
|
+
and str(getattr(authority, "credential_id", "")).strip()
|
|
2876
|
+
)
|
|
2877
|
+
if not allowed:
|
|
2878
|
+
raise DriveGovernanceError(
|
|
2879
|
+
"owner_charter_approval_authority_required",
|
|
2880
|
+
"scoped authenticated owner charter approval authority is required",
|
|
2881
|
+
)
|
|
2882
|
+
return (
|
|
2883
|
+
str(authority.principal_id).strip(),
|
|
2884
|
+
str(authority.credential_id).strip(),
|
|
2885
|
+
)
|
|
2886
|
+
|
|
2887
|
+
def decide_transition_request(
|
|
2888
|
+
self,
|
|
2889
|
+
request_id: str,
|
|
2890
|
+
*,
|
|
2891
|
+
decision: str,
|
|
2892
|
+
decision_id: str,
|
|
2893
|
+
expected_action_digest: str,
|
|
2894
|
+
expected_request_digest: str,
|
|
2895
|
+
authority: Any,
|
|
2896
|
+
now: Optional[datetime] = None,
|
|
2897
|
+
) -> Dict[str, Any]:
|
|
2898
|
+
"""Decide and converge one exact charter transition without grants."""
|
|
2899
|
+
|
|
2900
|
+
if self.mode not in {"bootstrap", "live"}:
|
|
2901
|
+
raise DriveGovernanceError(
|
|
2902
|
+
"transition_not_authoritative",
|
|
2903
|
+
"charter transitions require bootstrap or live mode",
|
|
2904
|
+
)
|
|
2905
|
+
principal, credential = self._validate_transition_decision_authority(
|
|
2906
|
+
authority,
|
|
2907
|
+
)
|
|
2908
|
+
observed = _as_utc(now)
|
|
2909
|
+
request, revision, transition, binding_current, use =\
|
|
2910
|
+
self._validated_transition_approval(request_id, now=observed)
|
|
2911
|
+
if expected_action_digest != request["action_digest"]:
|
|
2912
|
+
raise DriveGovernanceError(
|
|
2913
|
+
"stale_action_digest",
|
|
2914
|
+
"decision does not match the immutable charter transition",
|
|
2915
|
+
)
|
|
2916
|
+
if expected_request_digest != request["request_digest"]:
|
|
2917
|
+
raise DriveGovernanceError(
|
|
2918
|
+
"stale_request_digest",
|
|
2919
|
+
"decision does not match the displayed subject, presentation, and expiry",
|
|
2920
|
+
)
|
|
2921
|
+
if request["status"] == "pending" and not binding_current:
|
|
2922
|
+
raise DriveGovernanceError(
|
|
2923
|
+
"approval_binding_stale",
|
|
2924
|
+
"charter transition binding changed before the decision",
|
|
2925
|
+
)
|
|
2926
|
+
if self.approval_store is None:
|
|
2927
|
+
raise DriveGovernanceError(
|
|
2928
|
+
"approval_store_required", "approval authority store is unavailable",
|
|
2929
|
+
)
|
|
2930
|
+
self.approval_store.decide(
|
|
2931
|
+
request["request_id"],
|
|
2932
|
+
decision=decision,
|
|
2933
|
+
decision_id=decision_id,
|
|
2934
|
+
expected_action_digest=expected_action_digest,
|
|
2935
|
+
decided_by=principal,
|
|
2936
|
+
authority_evidence=f"scoped_principal:{principal}:{credential}",
|
|
2937
|
+
grant_scope=None,
|
|
2938
|
+
now=observed,
|
|
2939
|
+
)
|
|
2940
|
+
if str(decision).strip().lower() == "approve" and use is None:
|
|
2941
|
+
operation_id = "charter-decision:" + _digest({
|
|
2942
|
+
"request_id": request["request_id"],
|
|
2943
|
+
"decision_id": decision_id,
|
|
2944
|
+
})[:32]
|
|
2945
|
+
self.ratify_transition(
|
|
2946
|
+
revision.revision_id,
|
|
2947
|
+
transition=transition,
|
|
2948
|
+
approval_request_id=request["request_id"],
|
|
2949
|
+
operation_id=operation_id,
|
|
2950
|
+
authority=authority,
|
|
2951
|
+
now=observed,
|
|
2952
|
+
)
|
|
2953
|
+
return self.transition_approval_projection(
|
|
2954
|
+
request["request_id"], now=observed,
|
|
2955
|
+
)
|
|
2956
|
+
|
|
2957
|
+
@staticmethod
|
|
2958
|
+
def _validate_owner_authority(authority: Any) -> tuple[str, str]:
|
|
2959
|
+
allowed = bool(
|
|
2960
|
+
authority is not None
|
|
2961
|
+
and getattr(authority, "authenticated", False)
|
|
2962
|
+
and not getattr(authority, "legacy", False)
|
|
2963
|
+
and not getattr(authority, "anonymous", False)
|
|
2964
|
+
and callable(getattr(authority, "has_scope", None))
|
|
2965
|
+
and (
|
|
2966
|
+
authority.has_scope("approvals:decide")
|
|
2967
|
+
or authority.has_scope("charter:approval-decide")
|
|
2968
|
+
)
|
|
2969
|
+
and "owner" in set(getattr(authority, "audiences", ()))
|
|
2970
|
+
and str(getattr(authority, "principal_id", "")).strip()
|
|
2971
|
+
and str(getattr(authority, "credential_id", "")).strip()
|
|
2972
|
+
)
|
|
2973
|
+
if not allowed:
|
|
2974
|
+
raise DriveGovernanceError(
|
|
2975
|
+
"owner_authority_required",
|
|
2976
|
+
"scoped authenticated owner approval authority is required",
|
|
2977
|
+
)
|
|
2978
|
+
principal = str(authority.principal_id).strip()
|
|
2979
|
+
credential = str(authority.credential_id).strip()
|
|
2980
|
+
return principal, credential
|
|
2981
|
+
|
|
2982
|
+
def ratify_transition(
|
|
2983
|
+
self,
|
|
2984
|
+
revision_id: str,
|
|
2985
|
+
*,
|
|
2986
|
+
transition: str,
|
|
2987
|
+
approval_request_id: str,
|
|
2988
|
+
operation_id: str,
|
|
2989
|
+
authority: Any,
|
|
2990
|
+
now: Optional[datetime] = None,
|
|
2991
|
+
) -> Dict[str, Any]:
|
|
2992
|
+
"""Activate/revoke only from an exact owner-scoped approval decision."""
|
|
2993
|
+
|
|
2994
|
+
if self.mode not in {"bootstrap", "live"}:
|
|
2995
|
+
raise DriveGovernanceError(
|
|
2996
|
+
"transition_not_authoritative",
|
|
2997
|
+
"charter transitions require bootstrap or live mode",
|
|
2998
|
+
)
|
|
2999
|
+
principal, credential = self._validate_owner_authority(authority)
|
|
3000
|
+
normalized = str(transition or "").strip().lower()
|
|
3001
|
+
if normalized not in _TRANSITIONS:
|
|
3002
|
+
raise DriveGovernanceError(
|
|
3003
|
+
"invalid_transition", "transition must be activate or revoke",
|
|
3004
|
+
)
|
|
3005
|
+
request_id = _identifier(
|
|
3006
|
+
approval_request_id, "approval_request_id",
|
|
3007
|
+
)
|
|
3008
|
+
operation = _operation_id(operation_id)
|
|
3009
|
+
store = self._store()
|
|
3010
|
+
|
|
3011
|
+
# Exact retries remain idempotent after the lifecycle commit. A changed
|
|
3012
|
+
# request, revision, or transition using the same operation is refused.
|
|
3013
|
+
prior = store.transition_operation(operation)
|
|
3014
|
+
if prior is not None:
|
|
3015
|
+
if (
|
|
3016
|
+
prior["revision_id"] != revision_id
|
|
3017
|
+
or prior["transition"] != normalized
|
|
3018
|
+
or prior["approval_request_id"] != request_id
|
|
3019
|
+
):
|
|
3020
|
+
raise DriveGovernanceError(
|
|
3021
|
+
"operation_replay_conflict",
|
|
3022
|
+
"ratification operation replay changed its immutable first use",
|
|
3023
|
+
)
|
|
3024
|
+
projection = store.revision_projection(revision_id, now=now)
|
|
3025
|
+
projection.update({
|
|
3026
|
+
"enabled": True,
|
|
3027
|
+
"status": f"charter_{normalized}_replayed",
|
|
3028
|
+
"event_id": prior["event_id"],
|
|
3029
|
+
"replayed": True,
|
|
3030
|
+
})
|
|
3031
|
+
return projection
|
|
3032
|
+
|
|
3033
|
+
revision = store.get_revision(revision_id)
|
|
3034
|
+
if revision is None:
|
|
3035
|
+
raise DriveGovernanceError(
|
|
3036
|
+
"revision_unknown", "charter revision is unknown",
|
|
3037
|
+
)
|
|
3038
|
+
observed = _as_utc(now)
|
|
3039
|
+
current = store.active_revision(revision.charter_key, now=observed)
|
|
3040
|
+
if self.mode == "bootstrap" and (
|
|
3041
|
+
normalized != "activate"
|
|
3042
|
+
or current is not None
|
|
3043
|
+
or revision.parent_revision_id is not None
|
|
3044
|
+
or store.active_revisions(now=observed)
|
|
3045
|
+
):
|
|
3046
|
+
raise DriveGovernanceError(
|
|
3047
|
+
"bootstrap_transition_held",
|
|
3048
|
+
"bootstrap may activate only the first global root charter",
|
|
3049
|
+
)
|
|
3050
|
+
binding = charter_transition_binding(
|
|
3051
|
+
revision,
|
|
3052
|
+
transition=normalized,
|
|
3053
|
+
expected_active_revision_id=(
|
|
3054
|
+
current.revision_id if current is not None else None
|
|
3055
|
+
),
|
|
3056
|
+
)
|
|
3057
|
+
if self.approval_store is None:
|
|
3058
|
+
raise DriveGovernanceError(
|
|
3059
|
+
"approval_store_required", "approval authority store is unavailable",
|
|
3060
|
+
)
|
|
3061
|
+
request = self.approval_store.get_request(request_id, now=observed)
|
|
3062
|
+
if request is None:
|
|
3063
|
+
raise DriveGovernanceError(
|
|
3064
|
+
"approval_request_missing", "approval request was not found",
|
|
3065
|
+
)
|
|
3066
|
+
expires_at = _parse_time(request.get("expires_at"), field="approval expires_at")
|
|
3067
|
+
decided_at = (
|
|
3068
|
+
_parse_time(request.get("decided_at"), field="approval decided_at")
|
|
3069
|
+
if request.get("decided_at") else None
|
|
3070
|
+
)
|
|
3071
|
+
if (
|
|
3072
|
+
observed >= expires_at
|
|
3073
|
+
and not (
|
|
3074
|
+
request.get("status") == "approved"
|
|
3075
|
+
and request.get("decision") == "approve"
|
|
3076
|
+
and decided_at is not None
|
|
3077
|
+
and decided_at < expires_at
|
|
3078
|
+
)
|
|
3079
|
+
):
|
|
3080
|
+
raise DriveGovernanceError(
|
|
3081
|
+
"approval_expired", "charter approval window has expired",
|
|
3082
|
+
)
|
|
3083
|
+
if request.get("status") != "approved" or request.get("decision") != "approve":
|
|
3084
|
+
raise DriveGovernanceError(
|
|
3085
|
+
"approval_not_approved", "charter transition is not approved",
|
|
3086
|
+
)
|
|
3087
|
+
if (
|
|
3088
|
+
request.get("action_digest") != binding.action_digest
|
|
3089
|
+
or request.get("scope_digest") != binding.scope_digest
|
|
3090
|
+
or _canonical(request.get("scope")) != _canonical(binding.scope)
|
|
3091
|
+
):
|
|
3092
|
+
raise DriveGovernanceError(
|
|
3093
|
+
"approval_binding_mismatch",
|
|
3094
|
+
"approval does not bind the current immutable charter transition",
|
|
3095
|
+
)
|
|
3096
|
+
if (
|
|
3097
|
+
request.get("decided_by") != principal
|
|
3098
|
+
or not str(request.get("authority_evidence") or "").startswith(
|
|
3099
|
+
f"scoped_principal:{principal}:"
|
|
3100
|
+
)
|
|
3101
|
+
):
|
|
3102
|
+
raise DriveGovernanceError(
|
|
3103
|
+
"owner_authority_mismatch",
|
|
3104
|
+
"approval decision does not match the server-derived owner authority",
|
|
3105
|
+
)
|
|
3106
|
+
return store._apply_transition(
|
|
3107
|
+
revision,
|
|
3108
|
+
transition=normalized,
|
|
3109
|
+
binding=binding,
|
|
3110
|
+
approval_request=request,
|
|
3111
|
+
operation_id=operation,
|
|
3112
|
+
principal_id=principal,
|
|
3113
|
+
require_no_active_charter=(self.mode == "bootstrap"),
|
|
3114
|
+
now=observed,
|
|
3115
|
+
)
|
|
3116
|
+
|
|
3117
|
+
|
|
3118
|
+
class DriveRanker:
|
|
3119
|
+
"""Deterministically order goals that P3 has already admitted.
|
|
3120
|
+
|
|
3121
|
+
``policy_decision_resolver`` must resolve durable decision references from
|
|
3122
|
+
``CognitionSpineStore``. Caller-supplied booleans are never accepted as
|
|
3123
|
+
policy evidence. ``directive_manager`` is rechecked immediately before a
|
|
3124
|
+
ranking so a later boundary or global pause outranks an earlier P3 gate.
|
|
3125
|
+
"""
|
|
3126
|
+
|
|
3127
|
+
def __init__(
|
|
3128
|
+
self,
|
|
3129
|
+
store: DriveGovernanceStore,
|
|
3130
|
+
*,
|
|
3131
|
+
policy_decision_resolver: Optional[
|
|
3132
|
+
Callable[[str], Optional[Mapping[str, Any]]]
|
|
3133
|
+
],
|
|
3134
|
+
directive_manager: Any,
|
|
3135
|
+
charter_key: str = "default",
|
|
3136
|
+
) -> None:
|
|
3137
|
+
self.store = store
|
|
3138
|
+
self._resolve_policy = policy_decision_resolver
|
|
3139
|
+
self._directives = directive_manager
|
|
3140
|
+
self.charter_key = str(charter_key or "default")
|
|
3141
|
+
|
|
3142
|
+
@staticmethod
|
|
3143
|
+
def _batch(
|
|
3144
|
+
*,
|
|
3145
|
+
status: str,
|
|
3146
|
+
mode: str,
|
|
3147
|
+
applied: bool,
|
|
3148
|
+
charter: Optional[CharterRevisionV1],
|
|
3149
|
+
goals: Sequence[GoalRankInputV1],
|
|
3150
|
+
suggested: Iterable[str] = (),
|
|
3151
|
+
effective: Optional[Iterable[str]] = None,
|
|
3152
|
+
held: Iterable[str] = (),
|
|
3153
|
+
results: Iterable[GoalRankResultV1] = (),
|
|
3154
|
+
now: datetime,
|
|
3155
|
+
) -> RankingBatchV1:
|
|
3156
|
+
input_order = tuple(item.goal_id for item in goals)
|
|
3157
|
+
return RankingBatchV1(
|
|
3158
|
+
status=status,
|
|
3159
|
+
mode=mode,
|
|
3160
|
+
ranking_applied=applied,
|
|
3161
|
+
charter_revision_id=(charter.revision_id if charter else None),
|
|
3162
|
+
input_order=input_order,
|
|
3163
|
+
suggested_order=tuple(suggested),
|
|
3164
|
+
effective_order=(
|
|
3165
|
+
tuple(effective) if effective is not None else input_order
|
|
3166
|
+
),
|
|
3167
|
+
held_goal_ids=tuple(held),
|
|
3168
|
+
results=tuple(results),
|
|
3169
|
+
generated_at=_iso(now),
|
|
3170
|
+
)
|
|
3171
|
+
|
|
3172
|
+
def _p3_gate_state(
|
|
3173
|
+
self, goal: GoalRankInputV1,
|
|
3174
|
+
) -> tuple[str, tuple[str, ...]]:
|
|
3175
|
+
if self._resolve_policy is None or not goal.policy_decision_refs:
|
|
3176
|
+
return "p3_policy_unknown", ()
|
|
3177
|
+
stages: Dict[str, Mapping[str, Any]] = {}
|
|
3178
|
+
evidence: list[str] = []
|
|
3179
|
+
for reference in goal.policy_decision_refs:
|
|
3180
|
+
try:
|
|
3181
|
+
resolved = self._resolve_policy(reference)
|
|
3182
|
+
except Exception:
|
|
3183
|
+
return "p3_policy_unknown", ()
|
|
3184
|
+
if resolved is None or not isinstance(resolved, Mapping):
|
|
3185
|
+
return "p3_policy_unknown", ()
|
|
3186
|
+
payload = resolved.get("payload", resolved)
|
|
3187
|
+
if not isinstance(payload, Mapping):
|
|
3188
|
+
return "p3_policy_conflict", ()
|
|
3189
|
+
if (
|
|
3190
|
+
payload.get("decision_ref") != reference
|
|
3191
|
+
or payload.get("proposal_id") != goal.proposal_id
|
|
3192
|
+
or not isinstance(payload.get("allowed"), bool)
|
|
3193
|
+
):
|
|
3194
|
+
return "p3_policy_conflict", ()
|
|
3195
|
+
stage = str(payload.get("stage") or "")
|
|
3196
|
+
if stage in stages:
|
|
3197
|
+
return "p3_policy_conflict", ()
|
|
3198
|
+
stages[stage] = payload
|
|
3199
|
+
try:
|
|
3200
|
+
evidence.extend(_refs(
|
|
3201
|
+
payload.get("evidence_refs") or (),
|
|
3202
|
+
field="policy evidence", maximum=30,
|
|
3203
|
+
))
|
|
3204
|
+
except DriveGovernanceError:
|
|
3205
|
+
return "p3_policy_conflict", ()
|
|
3206
|
+
if any(stage not in stages for stage in _REQUIRED_P3_STAGES):
|
|
3207
|
+
return "p3_policy_unknown", tuple(dict.fromkeys(evidence))
|
|
3208
|
+
for stage in _REQUIRED_P3_STAGES:
|
|
3209
|
+
if not stages[stage]["allowed"]:
|
|
3210
|
+
return f"p3_{stage}_denied", tuple(dict.fromkeys(evidence))
|
|
3211
|
+
return "p3_eligible", tuple(dict.fromkeys(evidence))
|
|
3212
|
+
|
|
3213
|
+
def _directive_state(self, goal: GoalRankInputV1) -> tuple[str, str]:
|
|
3214
|
+
if self._directives is None:
|
|
3215
|
+
return "unknown", "directive_manager_unavailable"
|
|
3216
|
+
try:
|
|
3217
|
+
from apsimo.directives import Action
|
|
3218
|
+
|
|
3219
|
+
verdict = self._directives.check(Action(
|
|
3220
|
+
kind="project",
|
|
3221
|
+
text=f"{goal.title} {goal.objective_summary}",
|
|
3222
|
+
target=goal.title,
|
|
3223
|
+
high_risk=True,
|
|
3224
|
+
))
|
|
3225
|
+
except Exception:
|
|
3226
|
+
return "unknown", "directive_check_failed"
|
|
3227
|
+
if bool(getattr(verdict, "allowed", False)):
|
|
3228
|
+
return "allowed", str(
|
|
3229
|
+
getattr(verdict, "reason", "boundary_allowed")
|
|
3230
|
+
)[:500]
|
|
3231
|
+
reason = str(getattr(verdict, "reason", "boundary_denied"))[:500]
|
|
3232
|
+
if reason == "global_pause_active":
|
|
3233
|
+
return "paused", reason
|
|
3234
|
+
return "denied", reason
|
|
3235
|
+
|
|
3236
|
+
@staticmethod
|
|
3237
|
+
def _held_result(
|
|
3238
|
+
goal: GoalRankInputV1,
|
|
3239
|
+
*,
|
|
3240
|
+
state: str,
|
|
3241
|
+
policy_evidence: Iterable[str] = (),
|
|
3242
|
+
charter: Optional[CharterRevisionV1] = None,
|
|
3243
|
+
) -> GoalRankResultV1:
|
|
3244
|
+
scope = goal.scope
|
|
3245
|
+
if charter is not None:
|
|
3246
|
+
scope = _narrower_scope(scope, charter.scope)
|
|
3247
|
+
evidence = tuple(dict.fromkeys([
|
|
3248
|
+
*goal.evidence_refs, *policy_evidence,
|
|
3249
|
+
*(charter.evidence_refs if charter else ()),
|
|
3250
|
+
]))[:30]
|
|
3251
|
+
return GoalRankResultV1(
|
|
3252
|
+
goal_id=goal.goal_id,
|
|
3253
|
+
proposal_id=goal.proposal_id,
|
|
3254
|
+
state=state,
|
|
3255
|
+
eligible=False,
|
|
3256
|
+
total_score=None,
|
|
3257
|
+
contributions=(),
|
|
3258
|
+
drive_states={},
|
|
3259
|
+
evidence_refs=evidence,
|
|
3260
|
+
policy_decision_refs=goal.policy_decision_refs,
|
|
3261
|
+
rationale_summary=(
|
|
3262
|
+
"Goal was not ranked because a required deterministic gate "
|
|
3263
|
+
f"reported {state}."
|
|
3264
|
+
),
|
|
3265
|
+
scope=scope,
|
|
3266
|
+
)
|
|
3267
|
+
|
|
3268
|
+
def rank(
|
|
3269
|
+
self,
|
|
3270
|
+
goals: Sequence[GoalRankInputV1],
|
|
3271
|
+
*,
|
|
3272
|
+
mode: Optional[str] = None,
|
|
3273
|
+
now: Optional[datetime] = None,
|
|
3274
|
+
) -> RankingBatchV1:
|
|
3275
|
+
selected_mode = _normalized_mode(mode)
|
|
3276
|
+
observed = _as_utc(now)
|
|
3277
|
+
bounded_goals = tuple(goals)
|
|
3278
|
+
if len({item.goal_id for item in bounded_goals}) != len(bounded_goals):
|
|
3279
|
+
raise DriveGovernanceError(
|
|
3280
|
+
"duplicate_goal", "ranking input repeats a goal id",
|
|
3281
|
+
)
|
|
3282
|
+
if selected_mode == "off":
|
|
3283
|
+
return self._batch(
|
|
3284
|
+
status="off", mode="off", applied=False, charter=None,
|
|
3285
|
+
goals=bounded_goals, now=observed,
|
|
3286
|
+
)
|
|
3287
|
+
|
|
3288
|
+
# Pause is checked even when charter state is missing or expired. P7
|
|
3289
|
+
# must never turn an unavailable ranking policy into a pause bypass.
|
|
3290
|
+
if self._directives is None:
|
|
3291
|
+
return self._batch(
|
|
3292
|
+
status="pause_state_unknown", mode=selected_mode,
|
|
3293
|
+
applied=False, charter=None, goals=bounded_goals,
|
|
3294
|
+
now=observed,
|
|
3295
|
+
)
|
|
3296
|
+
directive_states: Dict[str, tuple[str, str]] = {}
|
|
3297
|
+
directive_unknown = False
|
|
3298
|
+
globally_paused = False
|
|
3299
|
+
for goal in bounded_goals:
|
|
3300
|
+
directive_states[goal.goal_id] = self._directive_state(goal)
|
|
3301
|
+
if directive_states[goal.goal_id][0] == "unknown":
|
|
3302
|
+
directive_unknown = True
|
|
3303
|
+
if directive_states[goal.goal_id][0] == "paused":
|
|
3304
|
+
globally_paused = True
|
|
3305
|
+
if directive_unknown:
|
|
3306
|
+
return self._batch(
|
|
3307
|
+
status="pause_state_unknown", mode=selected_mode,
|
|
3308
|
+
applied=False, charter=None, goals=bounded_goals,
|
|
3309
|
+
now=observed,
|
|
3310
|
+
)
|
|
3311
|
+
if globally_paused:
|
|
3312
|
+
results = tuple(
|
|
3313
|
+
self._held_result(goal, state="global_pause_active")
|
|
3314
|
+
for goal in bounded_goals
|
|
3315
|
+
)
|
|
3316
|
+
return self._batch(
|
|
3317
|
+
status="global_pause_active", mode=selected_mode,
|
|
3318
|
+
applied=False, charter=None, goals=bounded_goals,
|
|
3319
|
+
effective=(), held=(item.goal_id for item in bounded_goals),
|
|
3320
|
+
results=results, now=observed,
|
|
3321
|
+
)
|
|
3322
|
+
|
|
3323
|
+
charter = self.store.active_revision(self.charter_key, now=observed)
|
|
3324
|
+
if charter is None:
|
|
3325
|
+
return self._batch(
|
|
3326
|
+
status="charter_unknown", mode=selected_mode, applied=False,
|
|
3327
|
+
charter=None, goals=bounded_goals, now=observed,
|
|
3328
|
+
)
|
|
3329
|
+
budget = charter.ranking_budget
|
|
3330
|
+
if len(bounded_goals) > budget.max_goals:
|
|
3331
|
+
return self._batch(
|
|
3332
|
+
status="goal_budget_exceeded", mode=selected_mode,
|
|
3333
|
+
applied=False, charter=charter, goals=bounded_goals,
|
|
3334
|
+
now=observed,
|
|
3335
|
+
)
|
|
3336
|
+
results: list[GoalRankResultV1] = []
|
|
3337
|
+
total_signals_used = 0
|
|
3338
|
+
weights = charter.weight_map()
|
|
3339
|
+
drives: Dict[str, Optional[DriveV1]] = {
|
|
3340
|
+
drive_id: self.store.get_drive(drive_id) for drive_id in weights
|
|
3341
|
+
}
|
|
3342
|
+
for goal in bounded_goals:
|
|
3343
|
+
gate_state, policy_evidence = self._p3_gate_state(goal)
|
|
3344
|
+
if gate_state != "p3_eligible":
|
|
3345
|
+
results.append(self._held_result(
|
|
3346
|
+
goal, state=gate_state,
|
|
3347
|
+
policy_evidence=policy_evidence, charter=charter,
|
|
3348
|
+
))
|
|
3349
|
+
continue
|
|
3350
|
+
if directive_states[goal.goal_id][0] == "denied":
|
|
3351
|
+
results.append(self._held_result(
|
|
3352
|
+
goal,
|
|
3353
|
+
state="boundary_recheck_denied",
|
|
3354
|
+
policy_evidence=policy_evidence,
|
|
3355
|
+
charter=charter,
|
|
3356
|
+
))
|
|
3357
|
+
continue
|
|
3358
|
+
|
|
3359
|
+
result_scope = _narrower_scope(goal.scope, charter.scope)
|
|
3360
|
+
evidence: list[str] = list(dict.fromkeys([
|
|
3361
|
+
*goal.evidence_refs, *policy_evidence, *charter.evidence_refs,
|
|
3362
|
+
]))[:budget.max_evidence_refs_per_goal]
|
|
3363
|
+
drive_states: Dict[str, str] = {}
|
|
3364
|
+
contributions: list[DriveContributionV1] = []
|
|
3365
|
+
goal_signals = self.store.signals_for_goal(
|
|
3366
|
+
goal.goal_fingerprint, now=observed, include_expired=True,
|
|
3367
|
+
)
|
|
3368
|
+
grouped: Dict[str, list[DriveSignalV1]] = {}
|
|
3369
|
+
for item in goal_signals:
|
|
3370
|
+
grouped.setdefault(item.drive_id, []).append(item)
|
|
3371
|
+
|
|
3372
|
+
total_score = 0.0
|
|
3373
|
+
for drive_id, weight in sorted(weights.items()):
|
|
3374
|
+
drive = drives.get(drive_id)
|
|
3375
|
+
if drive is None:
|
|
3376
|
+
drive_state = "unknown"
|
|
3377
|
+
drive_states[drive_id] = drive_state
|
|
3378
|
+
contributions.append(DriveContributionV1(
|
|
3379
|
+
drive_id=drive_id, state=drive_state, weight=weight,
|
|
3380
|
+
raw_signal_value=0.0, mean_confidence=0.0,
|
|
3381
|
+
weighted_value=0.0, abs_budget=0.0,
|
|
3382
|
+
signal_refs=(), evidence_refs=(), rationale_summaries=(),
|
|
3383
|
+
))
|
|
3384
|
+
continue
|
|
3385
|
+
effective_drive_state = drive.state_at(observed)
|
|
3386
|
+
if effective_drive_state != "enabled":
|
|
3387
|
+
drive_states[drive_id] = effective_drive_state
|
|
3388
|
+
contributions.append(DriveContributionV1(
|
|
3389
|
+
drive_id=drive_id, state=effective_drive_state,
|
|
3390
|
+
weight=weight, raw_signal_value=0.0,
|
|
3391
|
+
mean_confidence=0.0, weighted_value=0.0,
|
|
3392
|
+
abs_budget=drive.max_abs_contribution,
|
|
3393
|
+
signal_refs=(), evidence_refs=(), rationale_summaries=(),
|
|
3394
|
+
))
|
|
3395
|
+
continue
|
|
3396
|
+
candidates = [
|
|
3397
|
+
item for item in grouped.get(drive_id, ())
|
|
3398
|
+
if item.state_at(observed) == "active"
|
|
3399
|
+
]
|
|
3400
|
+
limit = min(
|
|
3401
|
+
drive.max_signals_per_goal,
|
|
3402
|
+
budget.max_signals_per_drive,
|
|
3403
|
+
max(0, budget.max_total_signals - total_signals_used),
|
|
3404
|
+
)
|
|
3405
|
+
selected = candidates[:limit]
|
|
3406
|
+
if not selected:
|
|
3407
|
+
observed_states = {
|
|
3408
|
+
item.state_at(observed)
|
|
3409
|
+
for item in grouped.get(drive_id, ())
|
|
3410
|
+
}
|
|
3411
|
+
if candidates and limit == 0:
|
|
3412
|
+
drive_state = "budget_exhausted"
|
|
3413
|
+
elif "unknown" in observed_states:
|
|
3414
|
+
drive_state = "unknown"
|
|
3415
|
+
elif "disabled" in observed_states:
|
|
3416
|
+
drive_state = "disabled"
|
|
3417
|
+
elif "expired" in observed_states:
|
|
3418
|
+
drive_state = "expired"
|
|
3419
|
+
else:
|
|
3420
|
+
drive_state = "unknown"
|
|
3421
|
+
drive_states[drive_id] = drive_state
|
|
3422
|
+
contributions.append(DriveContributionV1(
|
|
3423
|
+
drive_id=drive_id, state=drive_state, weight=weight,
|
|
3424
|
+
raw_signal_value=0.0, mean_confidence=0.0,
|
|
3425
|
+
weighted_value=0.0,
|
|
3426
|
+
abs_budget=drive.max_abs_contribution,
|
|
3427
|
+
signal_refs=(), evidence_refs=(), rationale_summaries=(),
|
|
3428
|
+
))
|
|
3429
|
+
continue
|
|
3430
|
+
total_signals_used += len(selected)
|
|
3431
|
+
raw_value = sum(
|
|
3432
|
+
item.normalized_value for item in selected
|
|
3433
|
+
) / len(selected)
|
|
3434
|
+
mean_confidence = sum(
|
|
3435
|
+
item.confidence for item in selected
|
|
3436
|
+
) / len(selected)
|
|
3437
|
+
evidence_adjusted = sum(
|
|
3438
|
+
item.normalized_value * item.confidence for item in selected
|
|
3439
|
+
) / len(selected)
|
|
3440
|
+
weighted = max(
|
|
3441
|
+
-drive.max_abs_contribution,
|
|
3442
|
+
min(drive.max_abs_contribution, evidence_adjusted * weight),
|
|
3443
|
+
)
|
|
3444
|
+
weighted = round(weighted, 8)
|
|
3445
|
+
total_score += weighted
|
|
3446
|
+
drive_states[drive_id] = "active"
|
|
3447
|
+
raw_contribution_evidence = tuple(dict.fromkeys(
|
|
3448
|
+
ref for item in selected for ref in item.evidence_refs
|
|
3449
|
+
))
|
|
3450
|
+
visible_contribution_evidence: list[str] = []
|
|
3451
|
+
for reference in raw_contribution_evidence:
|
|
3452
|
+
if reference in evidence:
|
|
3453
|
+
visible_contribution_evidence.append(reference)
|
|
3454
|
+
elif len(evidence) < budget.max_evidence_refs_per_goal:
|
|
3455
|
+
evidence.append(reference)
|
|
3456
|
+
visible_contribution_evidence.append(reference)
|
|
3457
|
+
contribution_evidence = tuple(visible_contribution_evidence)
|
|
3458
|
+
for item in selected:
|
|
3459
|
+
result_scope = _narrower_scope(result_scope, item.scope)
|
|
3460
|
+
contributions.append(DriveContributionV1(
|
|
3461
|
+
drive_id=drive_id,
|
|
3462
|
+
state="active",
|
|
3463
|
+
weight=weight,
|
|
3464
|
+
raw_signal_value=round(raw_value, 8),
|
|
3465
|
+
mean_confidence=round(mean_confidence, 8),
|
|
3466
|
+
weighted_value=weighted,
|
|
3467
|
+
abs_budget=drive.max_abs_contribution,
|
|
3468
|
+
signal_refs=tuple(item.signal_id for item in selected),
|
|
3469
|
+
evidence_refs=contribution_evidence,
|
|
3470
|
+
rationale_summaries=tuple(
|
|
3471
|
+
item.rationale_summary for item in selected
|
|
3472
|
+
),
|
|
3473
|
+
))
|
|
3474
|
+
evidence_refs = tuple(evidence)
|
|
3475
|
+
results.append(GoalRankResultV1(
|
|
3476
|
+
goal_id=goal.goal_id,
|
|
3477
|
+
proposal_id=goal.proposal_id,
|
|
3478
|
+
state="ranked",
|
|
3479
|
+
eligible=True,
|
|
3480
|
+
total_score=round(max(-1.0, min(1.0, total_score)), 8),
|
|
3481
|
+
contributions=tuple(contributions),
|
|
3482
|
+
drive_states=drive_states,
|
|
3483
|
+
evidence_refs=evidence_refs,
|
|
3484
|
+
policy_decision_refs=goal.policy_decision_refs,
|
|
3485
|
+
rationale_summary=(
|
|
3486
|
+
"Weighted score uses only active, unexpired, "
|
|
3487
|
+
"evidence-referenced drive signals within the ratified "
|
|
3488
|
+
"charter budgets."
|
|
3489
|
+
),
|
|
3490
|
+
scope=result_scope,
|
|
3491
|
+
))
|
|
3492
|
+
|
|
3493
|
+
eligible = [item for item in results if item.eligible]
|
|
3494
|
+
ordered = sorted(
|
|
3495
|
+
eligible,
|
|
3496
|
+
key=lambda item: (
|
|
3497
|
+
-(item.total_score if item.total_score is not None else -1.0),
|
|
3498
|
+
item.goal_id,
|
|
3499
|
+
),
|
|
3500
|
+
)
|
|
3501
|
+
suggested = tuple(item.goal_id for item in ordered)
|
|
3502
|
+
held = tuple(item.goal_id for item in results if not item.eligible)
|
|
3503
|
+
applied = selected_mode == "live"
|
|
3504
|
+
effective = suggested if applied else tuple(
|
|
3505
|
+
item.goal_id for item in bounded_goals
|
|
3506
|
+
)
|
|
3507
|
+
return self._batch(
|
|
3508
|
+
status="ranked" if applied else "shadow_ranked",
|
|
3509
|
+
mode=selected_mode,
|
|
3510
|
+
applied=applied,
|
|
3511
|
+
charter=charter,
|
|
3512
|
+
goals=bounded_goals,
|
|
3513
|
+
suggested=suggested,
|
|
3514
|
+
effective=effective,
|
|
3515
|
+
held=held,
|
|
3516
|
+
results=results,
|
|
3517
|
+
now=observed,
|
|
3518
|
+
)
|
|
3519
|
+
|
|
3520
|
+
|
|
3521
|
+
__all__ = [
|
|
3522
|
+
"CHARTER_TRANSITION_APPROVAL_PROJECTION_SCHEMA",
|
|
3523
|
+
"CharterAdmissionConstraintsV1", "CharterRevisionV1",
|
|
3524
|
+
"DriveContributionV1", "DriveGovernance",
|
|
3525
|
+
"DriveGovernanceError", "DriveGovernanceStore", "DriveRanker",
|
|
3526
|
+
"DriveSignalV1", "DriveV1", "GoalRankInputV1", "GoalRankResultV1",
|
|
3527
|
+
"RankingBatchV1", "RankingBudgetV1", "ScopeV1",
|
|
3528
|
+
"charter_transition_binding", "charter_transition_presentation",
|
|
3529
|
+
"charter_transition_subject", "drive_governance_mode",
|
|
3530
|
+
]
|