diff --git a/.github/workflows/phase0c-workspace-deep-study-v2.yml b/.github/workflows/phase0c-workspace-deep-study-v2.yml new file mode 100644 index 00000000..7c965d10 --- /dev/null +++ b/.github/workflows/phase0c-workspace-deep-study-v2.yml @@ -0,0 +1,108 @@ +name: Phase 0C Workspace Deep Study V2 + +on: + pull_request: + paths: + - "design/donors/openai-chatgpt-workspace-deep-study.md" + - "design/candidates/PTAH-WORKSPACE-OPERATIONS-PROFILE-V2.md" + - "design/candidates/workspace-operations-profile-v2.json" + - "design/candidates/workspace-operations-gap-map-v2.json" + - "design/candidates/fixtures/workspace-operations-fixtures-v2.json" + - "tools/check_workspace_operations_donor_v2.py" + - "tools/test_check_workspace_operations_donor_v2.py" + - ".github/workflows/phase0c-workspace-deep-study-v2.yml" + - "design/donors/openai-chatgpt-projects-work.md" + - "design/candidates/ai-project-workspace-profile.json" + - "contracts/generated/catalog-index.json" + workflow_dispatch: + +permissions: + contents: read + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Check out exact pull-request head + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + fetch-depth: 1 + + - name: Capture exact head + id: head + shell: bash + run: | + set -euo pipefail + HEAD_SHA="$(git rev-parse HEAD)" + EXPECTED_SHA="${{ github.event.pull_request.head.sha || github.sha }}" + test "$HEAD_SHA" = "$EXPECTED_SHA" + echo "sha=$HEAD_SHA" >> "$GITHUB_OUTPUT" + printf '%s\n' "$HEAD_SHA" > exact-head.txt + + - name: Run 26 valid and adversarial cases + shell: bash + run: | + set -euo pipefail + python3 tools/test_check_workspace_operations_donor_v2.py -v 2>&1 | tee regressions.txt + + - name: Validate deep Workspace study + shell: bash + run: | + set -euo pipefail + python3 tools/check_workspace_operations_donor_v2.py \ + --repo-root . \ + --output workspace-deep-study-validation.json \ + | tee validator-output.txt + + - name: Bind exact-head evidence + shell: bash + run: | + set -euo pipefail + python3 - <<'PY' + import hashlib + import json + from pathlib import Path + + report = json.loads(Path("workspace-deep-study-validation.json").read_text()) + if report["status"] != "pass": + raise SystemExit("validation did not pass") + if report["study_method"] != "10 primary + 10 independent verifier lanes": + raise SystemExit("study method mismatch") + if report["mechanical_capability_count"] != 22: + raise SystemExit("mechanical capability count mismatch") + if report["gap_mapping_count"] != 28: + raise SystemExit("gap mapping count mismatch") + if report["fixture_count"] != 20: + raise SystemExit("fixture count mismatch") + for key in ("new_core_entity_required", "frozen_contract_change_required", "runtime_implementation_authorized"): + if report[key] is not False: + raise SystemExit(f"forbidden authority state: {key}") + report_bytes = Path("workspace-deep-study-validation.json").read_bytes() + evidence = { + "schema_version": "0.1.0", + "record_type": "ptah.phase0c.workspace_deep_study_exact_head_evidence", + "head_sha": Path("exact-head.txt").read_text().strip(), + "validation_sha256": hashlib.sha256(report_bytes).hexdigest(), + "regression_case_count": 26, + "primary_lanes": 10, + "independent_verifier_lanes": 10, + "new_core_entity_required": False, + "frozen_contract_change_required": False, + "runtime_implementation_authorized": False, + } + Path("exact-head-evidence.json").write_text(json.dumps(evidence, indent=2, sort_keys=True) + "\n") + PY + + - name: Upload retained evidence + uses: actions/upload-artifact@v4 + with: + name: phase0c-workspace-deep-study-v2-${{ steps.head.outputs.sha }} + path: | + exact-head.txt + exact-head-evidence.json + workspace-deep-study-validation.json + validator-output.txt + regressions.txt + if-no-files-found: error + retention-days: 90 diff --git a/design/candidates/PTAH-WORKSPACE-OPERATIONS-PROFILE-V2.md b/design/candidates/PTAH-WORKSPACE-OPERATIONS-PROFILE-V2.md new file mode 100644 index 00000000..23a46f5a --- /dev/null +++ b/design/candidates/PTAH-WORKSPACE-OPERATIONS-PROFILE-V2.md @@ -0,0 +1,122 @@ +# Ptah Workspace Operations Profile V2 + +Status: candidate, non-operative +Source: deep observable study of the current ChatGPT Workspace plus official public product documentation +Relationship: compatible supplement to the accepted neutral AI Project Workspace profile + +## Fixed product boundary + +**Ptah is the world and machinery, not the thinker.** + +Ptah provides the neutral Workspace and mechanical capabilities needed by humans, Hunter, Sergeant, applications and other agents. It does not choose the job, interpret intent, rank sources, decide truth, approve results, issue a review verdict or choose the next action. + +## What this supplement adds + +The first AI Project Workspace profile concentrated on the project envelope: chats, files, instructions, project memory, shared work, long-running work, Canvas and schedules. + +This supplement studies the deeper operating contract visible in the workspace: + +1. typed and incrementally discoverable operation schemas; +2. mechanical effect classes for observe, draft, simulate, mutate, publish, destructive and external-side-effect operations; +3. separation between external Provider permission and local confirmation policy; +4. explicit external-reference, indexed, mounted, materialized and generated-file states; +5. progress Events, failed Attempts and partial Artifact retention; +6. stable handles for results too large for one active Session; +7. replaceable cards, tables, charts and previews as Views; +8. one-off, recurring and condition-dependent schedules with exact or flexible timing semantics; +9. exact Revision and target-head preconditions for safe mutation; +10. distinct succeeded, failed, declined, cancelled, not-run and partially-completed results; +11. staged observe/draft/simulate/execute/verify workflows; +12. source, account and permission provenance for connected systems; +13. stable cross-device and cross-provider continuation; +14. honest product, Provider and execution-limit reporting. + +## Operation descriptor + +A Facility or Provider operation should expose mechanically inspectable metadata such as: + +- operation identity and schema version; +- argument and result schemas; +- effect class; +- required Grant; +- exact supported preconditions; +- expected Receipt states; +- limits and timeout behaviour; +- source Provider and account boundary; +- whether it can be discovered lazily; +- whether it creates or requires materialized bytes. + +This is a profile-level contract over existing primitives. It does not create a new Core entity. + +## File truth + +Ptah must distinguish a reference from bytes it actually holds: + +```text +external_reference +→ indexed_reference +→ mounted_read_only or materialized_copy +→ generated_artifact where applicable +``` + +A connector file reference must never be presented as a local path until an explicit mount or materialization Activity has succeeded and produced a Receipt. + +## Action truth + +A submitted operation is not proof of its effect. + +```text +submitted Activity +→ Attempt +→ Provider response or failure +→ optional independent post-condition verification +→ final Receipt +``` + +Draft and publish remain separate. Approval and external permission remain separate. Retry creates a new Attempt and preserves the failed Attempt. + +## View truth + +A message card, table, chart, file preview, mobile record or progress widget is a View over an underlying Object, Artifact, Activity or Receipt. + +The View may be replaced without changing the record. A green card cannot accept a candidate. A hidden card cannot erase a failure. Applications may render the same record differently while retaining one identity and provenance chain. + +## Scheduling truth + +Ptah may mechanically run schedules supplied by a caller: + +- one-off; +- recurring; +- condition watch; +- exact time; +- flexible window; +- condition-dependent checks. + +Each scheduled Activity receives exact caller-specified Workspace, Recipe, input Revision, Provider and Grant references. It does not inherit hidden context. The caller owns the schedule's purpose and desired outcome. + +## Semantic and authority ownership + +The following remain outside Ptah: + +- intent interpretation; +- job definition; +- context and source selection; +- source trust and authority; +- Provider and tool choice where more than one is compatible; +- semantic worker-output reconciliation; +- approval or rejection; +- result acceptance and canonical promotion; +- next-action choice. + +Ptah can execute a submitted search, merge, review or approval workflow. It does not supply the semantic decision. + +## Contract conclusion + +The deep study found: + +- 16 behaviours covered directly by the neutral substrate; +- 6 behaviours composed by caller applications; +- 0 justified Core extensions; +- 6 product behaviours explicitly rejected or not adopted. + +No frozen WP01–WP14 contract is reopened. No runtime implementation is authorized. The supplement should be used later as an implementation and conformance profile for Workspace shells, Facility adapters, Activity progress, Artifact delivery and recovery interfaces. diff --git a/design/candidates/fixtures/workspace-operations-fixtures-v2.json b/design/candidates/fixtures/workspace-operations-fixtures-v2.json new file mode 100644 index 00000000..e4864693 --- /dev/null +++ b/design/candidates/fixtures/workspace-operations-fixtures-v2.json @@ -0,0 +1,154 @@ +{ + "schema_version": "0.1.0", + "record_type": "ptah.phase0c.workspace_operations_fixtures_candidate", + "profile_id": "ptah.workspace.operations.v2", + "study_method": { + "primary_lanes": 10, + "independent_verifier_lanes": 10 + }, + "fixtures": [ + { + "id": "lazy-operation-discovery", + "kind": "positive", + "given": "A caller requests GitHub pull-request operations without loading every installed Facility schema.", + "expected": "return_bounded_operation_descriptors", + "proof": ["only matching operation schemas are returned", "schema version and Provider identity are retained", "Ptah does not choose the operation"] + }, + { + "id": "effect-class-grant-denial", + "kind": "negative", + "given": "A Grant permits observe operations but a caller submits a destructive operation.", + "expected": "deny_before_execution", + "proof": ["no Attempt with external effect begins", "denial Receipt names the effect class and configured boundary"] + }, + { + "id": "external-permission-preservation", + "kind": "positive", + "given": "A connected account can read only a subset of external records.", + "expected": "return_only_provider_permitted_records", + "proof": ["Ptah does not expand external access", "Provider account and scope provenance remain visible"] + }, + { + "id": "confirmation-does-not-expand-access", + "kind": "negative", + "given": "A human approves one app action whose external account lacks the requested scope.", + "expected": "external_access_still_denied", + "proof": ["approval does not create Provider permission", "failed effect is retained separately from the approval record"] + }, + { + "id": "reference-is-not-materialized-by-name", + "kind": "negative", + "given": "A connector returns a file reference but no bytes have been mounted or copied into the execution Environment.", + "expected": "no_local_path_claim", + "proof": ["availability state remains external_reference", "a separate materialization Activity is required"] + }, + { + "id": "materialization-retains-provenance", + "kind": "positive", + "given": "A caller explicitly materializes an external file for a sandboxed Activity.", + "expected": "create_materialized_revision", + "proof": ["source Provider and external identity remain linked", "digest and materialization Receipt are retained"] + }, + { + "id": "partial-output-survives-failure", + "kind": "positive", + "given": "A long-running Activity produces two Artifacts and then fails.", + "expected": "retain_failed_attempt_and_partial_artifacts", + "proof": ["failed Attempt remains visible", "partial Artifacts retain lineage", "no success claim is generated"] + }, + { + "id": "large-result-resource-handle", + "kind": "positive", + "given": "A Facility returns a result larger than the active Session budget.", + "expected": "retain_stable_handle_and_bounded_access", + "proof": ["result can be paged or searched", "bounded reads cite the same retained result identity"] + }, + { + "id": "exact-revision-conflict", + "kind": "negative", + "given": "A write Activity targets Revision A but the Object moved to Revision B before execution.", + "expected": "fail_precondition", + "proof": ["Revision B is not overwritten", "conflict Receipt retains both expected and observed revision identities"] + }, + { + "id": "draft-before-publish", + "kind": "positive", + "given": "A caller asks an application to prepare a customer message without sending it.", + "expected": "create_draft_artifact_only", + "proof": ["draft and publish are separate Activities", "no external-side-effect Receipt exists"] + }, + { + "id": "invocation-is-not-success", + "kind": "negative", + "given": "A tool call was submitted but the Provider returned no completion response.", + "expected": "not_run_or_unknown_effect_not_success", + "proof": ["no succeeded Receipt is fabricated", "verification may be requested as a separate Activity"] + }, + { + "id": "declined-failed-cancelled-distinction", + "kind": "positive", + "given": "Three Attempts respectively receive human denial, Provider failure and caller cancellation.", + "expected": "three_distinct_result_states", + "proof": ["declined, failed and cancelled remain distinguishable", "none is rewritten as successful"] + }, + { + "id": "render-independent-view", + "kind": "positive", + "given": "One Receipt is displayed as a chat card, table and mobile summary.", + "expected": "same_underlying_receipt", + "proof": ["all Views cite the same record identity", "removing one View does not remove the Receipt"] + }, + { + "id": "view-cannot-promote-authority", + "kind": "negative", + "given": "A UI card labels a candidate green while the underlying acceptance record remains pending.", + "expected": "underlying_state_remains_pending", + "proof": ["View styling creates no acceptance record", "caller must issue the authority decision"] + }, + { + "id": "condition-watch-no-notification", + "kind": "positive", + "given": "A condition-triggered Activity checks its source and the configured condition is false.", + "expected": "retain_check_without_user_notification", + "proof": ["check Attempt is retained", "no notification Artifact is emitted"] + }, + { + "id": "scheduled-task-exact-input-boundary", + "kind": "negative", + "given": "A scheduled Activity requests a Workspace file that was not supplied in its immutable input set.", + "expected": "deny_unconfigured_input", + "proof": ["schedule does not inherit hidden Session context", "missing input is reported"] + }, + { + "id": "cross-device-resume", + "kind": "positive", + "given": "A Session created on mobile is resumed through a desktop application using the same Workspace records.", + "expected": "resume_same_session_and_artifacts", + "proof": ["stable identities survive UI replacement", "Provider and device changes create no authority change"] + }, + { + "id": "semantic-merge-remains-caller-owned", + "kind": "negative", + "given": "Two independent workers return conflicting conclusions.", + "expected": "retain_both_without_ptah_verdict", + "proof": ["Ptah may execute a caller-supplied merge Recipe", "Ptah does not decide which conclusion is correct"] + }, + { + "id": "resource-limit-is-visible", + "kind": "positive", + "given": "An Activity exceeds its configured execution or product limit.", + "expected": "retain_limit_receipt", + "proof": ["limit class and measured value are reported", "Ptah does not silently reduce semantic scope"] + }, + { + "id": "retry-preserves-prior-attempt", + "kind": "negative", + "given": "A configured retry begins after a failed Attempt.", + "expected": "new_attempt_without_erasure", + "proof": ["failed Attempt remains retained", "retry has a new identity and links to its predecessor"] + } + ], + "new_core_entity_required": false, + "frozen_contract_change_required": false, + "runtime_implementation_authorized": false +} diff --git a/design/candidates/workspace-operations-gap-map-v2.json b/design/candidates/workspace-operations-gap-map-v2.json new file mode 100644 index 00000000..61ec7573 --- /dev/null +++ b/design/candidates/workspace-operations-gap-map-v2.json @@ -0,0 +1,172 @@ +{ + "schema_version": "0.1.0", + "record_type": "ptah.phase0c.workspace_operations_gap_map_candidate", + "profile_id": "ptah.workspace.operations.v2", + "status": "candidate_complete_no_contract_reopen", + "summary": { + "covered_by_neutral_substrate": 16, + "caller_application_composition": 6, + "candidate_core_extension": 0, + "rejected_or_not_adopted": 6 + }, + "mappings": [ + { + "capability": "bounded workspace envelope", + "classification": "covered_by_neutral_substrate", + "ptah_mapping": ["Workspace", "Grant"] + }, + { + "capability": "cross-device and cross-session continuation", + "classification": "covered_by_neutral_substrate", + "ptah_mapping": ["Workspace", "Session", "Activity", "Artifact"] + }, + { + "capability": "parallel work threads", + "classification": "covered_by_neutral_substrate", + "ptah_mapping": ["Workspace", "Session", "Event"] + }, + { + "capability": "reusable uploaded and generated file library", + "classification": "covered_by_neutral_substrate", + "ptah_mapping": ["Object", "Revision", "Artifact", "View"] + }, + { + "capability": "external reference versus materialized bytes", + "classification": "covered_by_neutral_substrate", + "ptah_mapping": ["Object", "Revision", "Artifact", "Facility", "Receipt"], + "improvement": "Expose availability and materialization state explicitly; never invent a local path from a connector reference." + }, + { + "capability": "typed operation catalog and schema discovery", + "classification": "covered_by_neutral_substrate", + "ptah_mapping": ["Facility", "Provider", "View", "Artifact"], + "improvement": "Permit capability schemas to be discovered incrementally so a caller need not load every operation at once." + }, + { + "capability": "operation effect classification", + "classification": "covered_by_neutral_substrate", + "ptah_mapping": ["Facility", "Recipe", "Grant", "Receipt"], + "improvement": "Advertise observe, draft, simulate, mutate, publish, destructive and external-side-effect classes as mechanical metadata." + }, + { + "capability": "external permission preservation and confirmation policy", + "classification": "covered_by_neutral_substrate", + "ptah_mapping": ["Provider", "Grant", "Activity", "Receipt"], + "improvement": "Separate external account access from the local rule deciding when confirmation is required." + }, + { + "capability": "visible progress and partial outputs", + "classification": "covered_by_neutral_substrate", + "ptah_mapping": ["Activity", "Attempt", "Event", "Artifact", "Receipt"] + }, + { + "capability": "large result handles with incremental access", + "classification": "covered_by_neutral_substrate", + "ptah_mapping": ["Artifact", "View", "Receipt", "Knowledge"], + "improvement": "Retain large results behind stable references that support paging, searching and bounded reads." + }, + { + "capability": "render-independent typed views", + "classification": "covered_by_neutral_substrate", + "ptah_mapping": ["View", "Object", "Artifact", "Receipt"], + "improvement": "A card, table, chart or preview is a View over the same underlying record and never becomes its authority." + }, + { + "capability": "one-off recurring and condition-triggered schedules", + "classification": "covered_by_neutral_substrate", + "ptah_mapping": ["Recipe", "Activity", "Event", "Grant"] + }, + { + "capability": "exact flexible and condition timing modes", + "classification": "covered_by_neutral_substrate", + "ptah_mapping": ["Recipe", "Activity", "Event"], + "improvement": "Retain whether a schedule is exact, windowed or condition-dependent instead of flattening all schedules into one timer." + }, + { + "capability": "exact revision preconditions and optimistic concurrency", + "classification": "covered_by_neutral_substrate", + "ptah_mapping": ["Revision", "Activity", "Attempt", "Receipt"], + "improvement": "Fail closed when the exact target revision, branch head or draft version moved." + }, + { + "capability": "read draft simulate execute verify lifecycle", + "classification": "covered_by_neutral_substrate", + "ptah_mapping": ["Recipe", "Activity", "Attempt", "Artifact", "Receipt"] + }, + { + "capability": "distinct failure decline cancellation and not-run states", + "classification": "covered_by_neutral_substrate", + "ptah_mapping": ["Attempt", "Event", "Receipt"], + "improvement": "Do not collapse user denial, policy rejection, execution failure, cancellation and missing execution into one generic error." + }, + { + "capability": "context selection and relevance", + "classification": "caller_application_composition", + "ptah_mapping": ["Knowledge", "View", "Artifact"], + "boundary": "Hunter, Sergeant or another caller chooses the query, relevance and packet contents." + }, + { + "capability": "tool and Provider selection", + "classification": "caller_application_composition", + "ptah_mapping": ["Facility", "Provider", "Recipe"], + "boundary": "Ptah reports compatible operations and availability; the caller chooses which capability serves the job." + }, + { + "capability": "semantic merge of worker outputs", + "classification": "caller_application_composition", + "ptah_mapping": ["Artifact", "Receipt", "Activity"], + "boundary": "Ptah can run a submitted merge Recipe but does not decide which conclusion is correct." + }, + { + "capability": "approval decision", + "classification": "caller_application_composition", + "ptah_mapping": ["Grant", "Activity", "Receipt"], + "boundary": "Ptah may block execution until a configured decision record exists; the human or approval application makes the decision." + }, + { + "capability": "result acceptance and canonical promotion", + "classification": "caller_application_composition", + "ptah_mapping": ["Artifact", "Revision", "Receipt"], + "boundary": "Acceptance labels and promotion decisions are caller-owned records." + }, + { + "capability": "next-action and schedule-purpose selection", + "classification": "caller_application_composition", + "ptah_mapping": ["Recipe", "Activity", "Artifact"], + "boundary": "The caller supplies the work, purpose and desired outcome." + }, + { + "capability": "hidden provider memory as canonical state", + "classification": "rejected_or_not_adopted", + "reason": "Provider-private state cannot replace inspectable caller-owned records." + }, + { + "capability": "implicit global operation access", + "classification": "rejected_or_not_adopted", + "reason": "Every external and local operation remains bounded by configured access and exact operation metadata." + }, + { + "capability": "chat transcript as sole operational database", + "classification": "rejected_or_not_adopted", + "reason": "Operational state must survive independently as typed records, Objects, Activities and Receipts." + }, + { + "capability": "silent materialization of external content", + "classification": "rejected_or_not_adopted", + "reason": "References, mounts and copied bytes require visibly different states and provenance." + }, + { + "capability": "success inferred from operation invocation", + "classification": "rejected_or_not_adopted", + "reason": "A final Receipt or independently checked post-condition is required to claim the effect." + }, + { + "capability": "UI view treated as authority", + "classification": "rejected_or_not_adopted", + "reason": "Views are replaceable renderings over underlying typed state." + } + ], + "conclusion": "The deeper workspace study improves Ptah profile composition and application contracts without adding a Core entity or reopening WP01-WP14.", + "frozen_contract_change_required": false, + "runtime_implementation_authorized": false +} diff --git a/design/candidates/workspace-operations-profile-v2.json b/design/candidates/workspace-operations-profile-v2.json new file mode 100644 index 00000000..cf837b82 --- /dev/null +++ b/design/candidates/workspace-operations-profile-v2.json @@ -0,0 +1,124 @@ +{ + "schema_version": "0.1.0", + "record_type": "ptah.phase0c.workspace_operations_profile_candidate", + "profile_id": "ptah.workspace.operations.v2", + "status": "candidate_non_operative", + "study_method": { + "name": "ten_for_two", + "primary_lanes": 10, + "independent_verifier_lanes": 10, + "purpose": "Study observable workspace behaviour, challenge each mapping against frozen Ptah contracts, and reconcile improvements without copying proprietary implementation." + }, + "source_boundary": { + "basis": [ + "direct observable product behaviour", + "official public OpenAI documentation", + "existing accepted Ptah Workspace donor and neutral-substrate correction" + ], + "openai_private_source_used": false, + "hidden_implementation_inferred": false, + "code_reuse": "none", + "integration": "behavioural and interaction-pattern adaptation only" + }, + "ptah_role": "neutral_workspace_and_execution_substrate", + "decision_authority": false, + "context_selection_authority": false, + "review_authority": false, + "approval_authority": false, + "new_core_entity_required": false, + "frozen_contract_change_required": false, + "mechanical_capabilities_to_borrow": [ + "bounded_workspace_envelope", + "parallel_session_continuity", + "cross_device_resume", + "reusable_artifact_library", + "external_reference_vs_materialized_object", + "typed_operation_catalog", + "lazy_capability_schema_discovery", + "operation_effect_classification", + "external_permission_preservation", + "configured_confirmation_policy", + "activity_progress_events", + "partial_output_and_failure_retention", + "large_result_resource_handles", + "paged_and_searchable_result_access", + "render_independent_typed_views", + "one_off_recurring_and_condition_schedules", + "exact_flexible_and_condition_timing_modes", + "optimistic_concurrency_and_exact_revision_preconditions", + "read_draft_simulate_execute_verify_lifecycle", + "distinct_declined_failed_cancelled_and_not_run_receipts", + "connector_source_and_account_provenance", + "explicit_resource_and_product_limit_reporting" + ], + "operation_effect_classes": [ + "observe", + "draft", + "simulate", + "mutate", + "publish", + "destructive", + "external_side_effect" + ], + "object_availability_states": [ + "external_reference", + "indexed_reference", + "mounted_read_only", + "materialized_copy", + "generated_artifact" + ], + "activity_result_states": [ + "succeeded", + "failed", + "declined", + "cancelled", + "not_run", + "partially_completed" + ], + "caller_owned_functions": [ + "intent interpretation", + "job definition", + "context search and selection", + "source authority and trust judgment", + "tool and Provider selection", + "semantic merge and reconciliation", + "approval and rejection", + "result acceptance", + "next-action selection", + "schedule purpose and desired outcome" + ], + "mechanical_composition": { + "workspace_and_threads": ["Workspace", "Session", "Grant"], + "files_and_reuse": ["Object", "Revision", "Artifact", "View"], + "work_and_progress": ["Recipe", "Activity", "Attempt", "Event", "Receipt"], + "tools_and_connections": ["Facility", "Provider", "Grant"], + "retrieval_and_large_results": ["Knowledge", "View", "Artifact", "Receipt"], + "scheduled_execution": ["Recipe", "Activity", "Event", "Grant"], + "interaction_surfaces": ["View", "Object", "Artifact", "Receipt"] + }, + "required_rules": [ + "A connector file reference is not silently represented as locally materialized bytes.", + "An operation invocation is not evidence that the external effect succeeded.", + "Approval prompting changes when execution may proceed; it does not expand the Provider's external access.", + "A View renders underlying state but does not become the authority for that state.", + "Retry creates a new Attempt and does not erase the failed Attempt.", + "Exact revision or head preconditions fail closed when the target moved.", + "Large results may be retained behind stable resource handles and accessed incrementally.", + "Scheduled Activities receive exact caller-specified inputs and configured Grants.", + "Ptah reports product, Provider and resource limits instead of silently changing the job.", + "Hunter or another caller performs semantic context selection, reconciliation and next-action choice." + ], + "rejected_patterns": [ + "hidden provider memory as canonical Workspace state", + "implicit global tool access", + "chat transcript as the sole operational database", + "silent materialization or copying of external files", + "success inferred from tool invocation alone", + "UI cards or previews treated as authority", + "Ptah-owned context ranking or semantic merge", + "Ptah approval, verdict or next-action authority" + ], + "existing_profile": "ptah.workspace.ai_project.v1", + "relationship_to_existing_profile": "deep compatible supplement; does not supersede the neutral-substrate correction", + "runtime_implementation_authorized": false +} diff --git a/design/donors/openai-chatgpt-projects-work.md b/design/donors/openai-chatgpt-projects-work.md index 77e21782..59d7b85f 100644 --- a/design/donors/openai-chatgpt-projects-work.md +++ b/design/donors/openai-chatgpt-projects-work.md @@ -130,6 +130,20 @@ This donor record: - does not authorize any runtime implementation; - does not weaken the current physical-host, closure-review or ADR-0033 gates. +## Deep observable Workspace supplement + +A second ten-for-two study now examines the deeper operating behaviour visible in the current Workspace: typed operation discovery, action-effect metadata, external permission preservation, reference-versus-materialized files, progress and partial output retention, large-result handles, render-independent Views, schedule modes, exact mutation preconditions and honest result states. + +The supplement is recorded in: + +- `design/donors/openai-chatgpt-workspace-deep-study.md` +- `design/candidates/PTAH-WORKSPACE-OPERATIONS-PROFILE-V2.md` +- `design/candidates/workspace-operations-profile-v2.json` +- `design/candidates/workspace-operations-gap-map-v2.json` +- `design/candidates/fixtures/workspace-operations-fixtures-v2.json` + +It is compatible with this corrected donor, uses no OpenAI private source code, adds no Core entity, reopens no frozen contract and preserves Ptah's neutral-platform boundary. + ## Conclusion ChatGPT Projects and Work remain useful as a behavioural donor for **applications** that let humans and agents remain inside one long-running project. Ptah supplies the neutral, inspectable and provider-independent platform underneath those applications; it does not decide what the project means or whether any result is correct. diff --git a/design/donors/openai-chatgpt-workspace-deep-study.md b/design/donors/openai-chatgpt-workspace-deep-study.md new file mode 100644 index 00000000..752b9df6 --- /dev/null +++ b/design/donors/openai-chatgpt-workspace-deep-study.md @@ -0,0 +1,372 @@ +# OpenAI ChatGPT Workspace — Deep Observable Behaviour Donor Study + +Status: Phase 0C candidate study +Classification: hosted-service and observable-interaction donor +Code reuse: none +Private source dependency: none +Existing authority: supplements `openai-chatgpt-projects-work.md`; it does not replace the neutral-substrate correction + +## Purpose + +This study answers a narrow question: **what can Ptah borrow from the workspace behaviour visible to its users and from OpenAI's public product documentation, without copying proprietary code or changing what Ptah is?** + +The source product proves useful interaction and operating patterns. It does not prove OpenAI's private implementation, internal data model, infrastructure, security architecture or algorithms. No hidden implementation is inferred. + +Ptah remains the already-decided product: + +- Ptah is a neutral Workspace, storage, execution, Facility, access, event, Artifact, checkpoint and recovery substrate. +- Hunter or another caller supplies intelligence, context selection, planning, coordination and next-action proposals. +- Sergeant independently reviews frozen candidates using Ptah resources. +- Humans or calling applications own intent, approval, acceptance, rejection and release. + +## Study method — ten for two + +The study used ten primary specialist lanes and ten independent verification lanes. This is an assistant execution method, not a Ptah or Sergeant architecture. + +| Pair | Primary observation lane | Independent contract check | +|---|---|---| +| 01 | Workspace envelope and cross-device continuity | Workspace isolation and identity stability | +| 02 | Files, Library and generated Artifacts | Reference/materialization and provenance | +| 03 | Tool/plugin discovery and typed schemas | Facility/Provider neutrality | +| 04 | Read/write actions and permission prompts | Grant boundary and external access preservation | +| 05 | Work progress, partial outputs and interruption | Activity/Attempt/Event/Receipt fidelity | +| 06 | Connected apps and synced sources | Source permission and account provenance | +| 07 | Cards, tables, previews and interactive output | View independence from authority | +| 08 | One-off, recurring and conditional tasks | Exact scheduled inputs and timing semantics | +| 09 | Drafting, mutation, exact-head updates and retries | Preconditions, concurrency and retained failures | +| 10 | Product limits, long results and conversation continuation | Honest limits, resource handles and recovery | + +The reconciled result is encoded in: + +- `workspace-operations-profile-v2.json` +- `workspace-operations-gap-map-v2.json` +- `fixtures/workspace-operations-fixtures-v2.json` + +## Public and observable sources + +Official documentation inspected: + +- https://help.openai.com/en/articles/10169521-projects-in-chatgpt +- https://help.openai.com/en/articles/11487775-connectors-in-chatgpt +- https://help.openai.com/en/articles/10847137 +- https://help.openai.com/en/articles/20001052-file-storage-and-library-in-chatgpt +- https://help.openai.com/en/articles/10291617-tasks-in-chatgpt +- https://help.openai.com/en/articles/20001275-chatgpt-work-and-codex +- https://help.openai.com/en/articles/9213685-extracting-insights-with-chatgpt-data-analysis +- https://help.openai.com/en/articles/11509118-admin-controls-security-and-compliance-for-plugins-and-apps +- https://help.openai.com/en/articles/20001256-plugins-in-chatgpt-and-codex +- https://help.openai.com/en/articles/20001247 + +Direct observable behaviour was also studied through this workspace's typed tools, connector references, sandboxed execution, generated Artifacts, incremental resource access, scheduled-task contracts, progress messages and exact mutation operations. + +## Improvement 1 — make operation capabilities discoverable and typed + +The workspace does not expose every possible operation as one unrestricted shell. Capabilities are grouped into tools and connectors with typed inputs and distinct operations. + +Ptah should expose a mechanical operation catalog for each Facility and Provider: + +- stable operation identity and schema version; +- required arguments and result type; +- effect class; +- required Grant; +- supported preconditions; +- expected Receipt states; +- resource limits; +- Provider and account boundary; +- whether the operation can be discovered lazily. + +A caller may query only the relevant capability schemas instead of loading the complete platform surface. Ptah returns descriptors; it does not choose the operation. + +Suggested effect classes: + +- `observe` +- `draft` +- `simulate` +- `mutate` +- `publish` +- `destructive` +- `external_side_effect` + +These are mechanical metadata used by Grants and application UIs. They are not Ptah judgments about whether the work should happen. + +## Improvement 2 — separate external access from confirmation policy + +A connected app's external permissions and the workspace's confirmation policy are different boundaries. + +Ptah should preserve this distinction: + +1. the external Provider/account determines what data or action is actually accessible; +2. a Ptah Grant determines whether the submitted operation may proceed locally; +3. a caller-owned approval application may create the decision record required by that Grant; +4. approval cannot expand the Provider's external permission; +5. denial, missing permission and execution failure produce different Receipts. + +This avoids a dangerous ambiguity where clicking “approve” appears to create access the connected account never had. + +## Improvement 3 — distinguish references from materialized bytes + +This workspace exposes an important operational truth: a connector file reference is not automatically a file inside the active execution environment. + +Ptah should expose explicit availability states: + +- `external_reference` +- `indexed_reference` +- `mounted_read_only` +- `materialized_copy` +- `generated_artifact` + +A local path may be claimed only after an explicit mount or materialization Activity produced a Receipt. The resulting Object/Revision must retain: + +- source Provider; +- external object identity; +- source revision or freshness marker where available; +- materialization Activity and Attempt; +- content digest; +- destination Environment or storage location; +- retention and deletion rule. + +This improves connector correctness, reproducibility and security without adding a new Core entity. + +## Improvement 4 — retain large results behind stable handles + +Tool and connector results can exceed the useful size of one chat response or one active context window. A workspace therefore benefits from stable result references that support incremental access. + +Ptah should allow a Receipt or Artifact to expose: + +- stable result identity; +- content digest; +- result size and media type; +- bounded line/range reads; +- paging cursor where appropriate; +- exact search within the retained result; +- source operation and Attempt; +- expiration or retention policy. + +The Session receives a View or excerpt. The full result remains a durable Artifact or Provider-backed reference. This prevents repeated downloads, truncation masquerading as completeness and conversation length from becoming data loss. + +## Improvement 5 — progress and partial outputs are first-class + +Long work should emit normal operational progress rather than vague “AI activity.” + +A submitted Activity may expose: + +- current stage; +- completed and pending sub-Activities; +- last Event; +- current Attempt; +- blocker or wait reason; +- partial Artifacts; +- questions requiring caller input; +- whether a configured confirmation is outstanding; +- truthful estimate only when one is available. + +Failure after partial production must retain both the failed Attempt and every valid partial Artifact. Retry creates a new Attempt linked to the failed one; it does not erase history. + +Ptah records and streams this mechanical state. Hunter or another application explains meaning and recommends what to do next. + +## Improvement 6 — use Views as replaceable renderings + +The workspace can show one result as a message, card, table, chart, preview, file link or interactive widget. + +Ptah should make this explicit: + +- the authoritative item is an Object, Revision, Artifact, Activity or Receipt; +- a View describes how an application renders or interacts with it; +- multiple Views may refer to the same underlying identity; +- deleting or replacing a View does not delete the underlying record; +- visual colour, success styling or placement cannot create authority; +- accessibility and mobile/desktop representations may differ without changing state. + +This supports rich interfaces while preventing UI cards from becoming a hidden operational database. + +## Improvement 7 — model schedules by execution semantics + +The observed workspace distinguishes one-off, recurring and condition-dependent tasks, including exact times, broader windows and checks that notify only when a condition becomes true. + +Ptah should retain both schedule kind and timing semantics: + +Schedule kind: + +- one-off; +- recurring; +- condition watch. + +Timing mode: + +- exact; +- flexible window; +- condition dependent. + +A scheduled Activity receives an explicit caller-owned input set: + +- Workspace identity; +- Recipe revision; +- immutable Object/Revision inputs; +- Provider connections or references; +- Grant; +- schedule definition; +- desired output or notification contract. + +It does not silently inherit unavailable chat context or unrelated Workspace files. If an input is absent, Ptah reports the missing dependency rather than inventing context. + +## Improvement 8 — make mutation preconditions explicit + +Reliable workspace actions frequently depend on the target still being the version that was inspected. + +Ptah should support exact preconditions such as: + +- Object Revision digest; +- document draft revision; +- repository branch head; +- message/thread identity; +- calendar event version; +- current state-machine state; +- Provider freshness token where available. + +If the target moved, the Activity fails closed with a conflict Receipt containing expected and observed identities. It must not silently overwrite newer work. + +This pattern is especially important for: + +- Git operations; +- collaborative documents; +- configuration changes; +- payment/order state; +- approvals; +- device policies; +- generated release Artifacts. + +## Improvement 9 — separate read, draft, simulate, execute and verify + +A capable workspace should not collapse every request into an immediate external mutation. + +The useful lifecycle is: + +```text +observe +→ draft or simulate +→ caller decision where configured +→ execute +→ verify post-condition +→ retain Receipt and Artifacts +``` + +Examples: + +- drafting an email is not sending it; +- creating a patch is not merging it; +- preparing an event is not inviting attendees; +- generating a payment request is not confirming payment; +- requesting a device action is not proof the device reached the desired state. + +Ptah can enforce this mechanical lifecycle through Recipes, Activities, Grants, Attempts and Receipts. It does not decide whether the draft is correct or the result should be accepted. + +## Improvement 10 — preserve honest result states and limits + +A workspace must distinguish: + +- `succeeded` +- `failed` +- `declined` +- `cancelled` +- `not_run` +- `partially_completed` + +Invocation alone is not success. A missing tool response is not proof that the external mutation happened. Where high assurance is required, a separate verification Activity checks the post-condition. + +Ptah should also report limits explicitly: + +- file size or count; +- context or result budget; +- schedule frequency; +- execution duration; +- CPU, memory, storage or network quota; +- Provider plan restriction; +- unavailable capability; +- retention expiry. + +It must not silently change the user's semantic scope to fit a limit. + +## Improvement 11 — keep synced knowledge permission-aware and provider-independent + +Connected and synced sources demonstrate the value of reducing context switching while retaining the source system's permissions. + +Ptah should support: + +- indexed Provider references; +- source account and tenant identity; +- permission-aware retrieval; +- freshness markers and sync checkpoints; +- deletion and access-revocation propagation; +- exact citations back to source Objects; +- an explicit difference between an index and canonical source bytes; +- replacement of one indexing Provider without losing the Workspace's caller-owned records. + +Hunter or the calling application chooses the query, relevance policy and context packet. Ptah only performs the configured retrieval and returns evidence-bearing results. + +## Improvement 12 — preserve continuity independently of one interface or model + +A strong workspace lets work continue across phone, web, desktop, different interfaces and replaceable intelligence Providers. + +Ptah should preserve stable identities for: + +- Workspace; +- Session; +- Activity; +- Attempt; +- Object and Revision; +- Artifact; +- Provider connection; +- Receipt; +- caller-owned handoff or checkpoint. + +The application may render the same records differently on each device. A model change or Session compaction must not rewrite operational history. + +For long conversations, the caller may create a semantic handoff Artifact. Ptah stores it, links it to its source records and exposes it to the next Session. Ptah does not authoritatively decide what the handoff means. + +## What this study does not borrow + +The following are explicitly rejected: + +- assistant identity or personality as Workspace identity; +- hidden model reasoning as operational authority; +- hidden provider memory as canonical state; +- implicit global access to tools or external services; +- transcript text as the sole database; +- automatic context selection treated as truth; +- silent file copying or materialization; +- success inferred from invocation; +- provider-specific hosted assumptions as mandatory architecture; +- a UI preview, card or green badge as approval; +- Ptah choosing work, ranking truth, issuing verdicts or deciding the next action. + +## Contract impact + +The study found **no justified new Core entity** and **no reason to reopen WP01–WP14**. + +The improvements compose from frozen Ptah records and profile rules: + +- Workspace, Session and Grant for the bounded envelope; +- Object, Revision, Artifact and View for files and renderings; +- Recipe, Activity, Attempt, Event and Receipt for work and progress; +- Facility and Provider for typed external and local capabilities; +- Knowledge and View for retrieval and incremental result access. + +Some implementation details become profile requirements or operation metadata, including effect class, precondition, materialization state, timing mode, result state and stable resource-handle behaviour. These are implementation and conformance concerns, not new product authority. + +## Final recommendation + +Adopt the observable workspace patterns as a **deep behavioural profile supplement**: + +- borrow the bounded project envelope; +- borrow typed, discoverable capabilities; +- borrow explicit action effects and confirmation boundaries; +- borrow reusable files and generated Artifacts; +- improve reference/materialization truth; +- improve progress and partial-result retention; +- improve exact mutation preconditions; +- improve scheduling semantics; +- improve render-independent Views; +- improve honest failure and limit reporting; +- keep every semantic and authority decision outside Ptah. + +This donor study is non-operative. It does not authorize runtime implementation, reopen frozen contracts, accept ADR-0033 or claim P01 physical-host proof. diff --git a/tools/check_workspace_operations_donor_v2.py b/tools/check_workspace_operations_donor_v2.py new file mode 100644 index 00000000..1d32fd0c --- /dev/null +++ b/tools/check_workspace_operations_donor_v2.py @@ -0,0 +1,277 @@ +#!/usr/bin/env python3 +"""Validate the non-operative deep Workspace operations donor study.""" +from __future__ import annotations + +import argparse +import hashlib +import json +from collections import Counter +from pathlib import Path +from typing import Any + +PROFILE_ID = "ptah.workspace.operations.v2" +EXPECTED_SUMMARY = { + "covered_by_neutral_substrate": 16, + "caller_application_composition": 6, + "candidate_core_extension": 0, + "rejected_or_not_adopted": 6, +} +EXPECTED_EFFECTS = { + "observe", "draft", "simulate", "mutate", "publish", + "destructive", "external_side_effect", +} +EXPECTED_AVAILABILITY = { + "external_reference", "indexed_reference", "mounted_read_only", + "materialized_copy", "generated_artifact", +} +EXPECTED_RESULTS = { + "succeeded", "failed", "declined", "cancelled", "not_run", + "partially_completed", +} +EXPECTED_FIXTURES = { + "lazy-operation-discovery", + "effect-class-grant-denial", + "external-permission-preservation", + "confirmation-does-not-expand-access", + "reference-is-not-materialized-by-name", + "materialization-retains-provenance", + "partial-output-survives-failure", + "large-result-resource-handle", + "exact-revision-conflict", + "draft-before-publish", + "invocation-is-not-success", + "declined-failed-cancelled-distinction", + "render-independent-view", + "view-cannot-promote-authority", + "condition-watch-no-notification", + "scheduled-task-exact-input-boundary", + "cross-device-resume", + "semantic-merge-remains-caller-owned", + "resource-limit-is-visible", + "retry-preserves-prior-attempt", +} +OFFICIAL_URLS = { + "https://help.openai.com/en/articles/10169521-projects-in-chatgpt", + "https://help.openai.com/en/articles/11487775-connectors-in-chatgpt", + "https://help.openai.com/en/articles/10847137", + "https://help.openai.com/en/articles/20001052-file-storage-and-library-in-chatgpt", + "https://help.openai.com/en/articles/10291617-tasks-in-chatgpt", + "https://help.openai.com/en/articles/20001275-chatgpt-work-and-codex", + "https://help.openai.com/en/articles/9213685-extracting-insights-with-chatgpt-data-analysis", + "https://help.openai.com/en/articles/11509118-admin-controls-security-and-compliance-for-plugins-and-apps", + "https://help.openai.com/en/articles/20001256-plugins-in-chatgpt-and-codex", + "https://help.openai.com/en/articles/20001247", +} + + +class StudyError(RuntimeError): + pass + + +def load_json(path: Path) -> dict[str, Any]: + try: + value = json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as exc: + raise StudyError(f"invalid JSON: {path}") from exc + if not isinstance(value, dict): + raise StudyError(f"top-level JSON object required: {path}") + return value + + +def require(text: str, needle: str, label: str) -> None: + if needle not in text: + raise StudyError(f"{label}: missing required text: {needle}") + + +def sha256(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def validate(repo_root: Path) -> dict[str, Any]: + root = repo_root.resolve() + paths = { + "readme": root / "README.md", + "existing_donor": root / "design/donors/openai-chatgpt-projects-work.md", + "existing_profile": root / "design/candidates/ai-project-workspace-profile.json", + "study": root / "design/donors/openai-chatgpt-workspace-deep-study.md", + "profile": root / "design/candidates/workspace-operations-profile-v2.json", + "gap": root / "design/candidates/workspace-operations-gap-map-v2.json", + "fixtures": root / "design/candidates/fixtures/workspace-operations-fixtures-v2.json", + "catalog": root / "contracts/generated/catalog-index.json", + } + missing = [name for name, path in paths.items() if not path.is_file()] + if missing: + raise StudyError(f"required files missing: {', '.join(sorted(missing))}") + + readme = paths["readme"].read_text(encoding="utf-8") + require(readme, "Runtime implementation is not authorized", "README") + + existing_donor = paths["existing_donor"].read_text(encoding="utf-8") + require(existing_donor, "does not assign context, authority, review or approval decisions to Ptah", "existing donor") + existing_profile = load_json(paths["existing_profile"]) + if existing_profile.get("ptah_role") != "neutral_workspace_and_execution_substrate": + raise StudyError("existing neutral Workspace correction is not preserved") + if existing_profile.get("runtime_implementation_authorized") is not False: + raise StudyError("existing profile unexpectedly authorizes runtime implementation") + + study = paths["study"].read_text(encoding="utf-8") + for url in OFFICIAL_URLS: + require(study, url, "official source set") + for token in ( + "ten primary specialist lanes and ten independent verification lanes", + "Ptah remains the already-decided product", + "no justified new Core entity", + "no reason to reopen WP01–WP14", + "does not authorize runtime implementation", + "connector file reference is not automatically a file inside the active execution environment", + "Invocation alone is not success", + "Views as replaceable renderings", + ): + require(study, token, "deep study") + + profile = load_json(paths["profile"]) + if profile.get("record_type") != "ptah.phase0c.workspace_operations_profile_candidate": + raise StudyError("profile record type mismatch") + if profile.get("profile_id") != PROFILE_ID: + raise StudyError("profile identity mismatch") + if profile.get("status") != "candidate_non_operative": + raise StudyError("profile must remain candidate and non-operative") + for key in ( + "decision_authority", "context_selection_authority", "review_authority", + "approval_authority", "new_core_entity_required", + "frozen_contract_change_required", "runtime_implementation_authorized", + ): + if profile.get(key) is not False: + raise StudyError(f"profile boundary must remain false: {key}") + if profile.get("ptah_role") != "neutral_workspace_and_execution_substrate": + raise StudyError("neutral Ptah role missing") + method = profile.get("study_method") + if not isinstance(method, dict) or method.get("name") != "ten_for_two": + raise StudyError("ten-for-two method record missing") + if method.get("primary_lanes") != 10 or method.get("independent_verifier_lanes") != 10: + raise StudyError("ten-for-two lane count mismatch") + source = profile.get("source_boundary") + if not isinstance(source, dict): + raise StudyError("source boundary missing") + if source.get("openai_private_source_used") is not False or source.get("hidden_implementation_inferred") is not False: + raise StudyError("study cannot claim private source or hidden implementation") + if source.get("code_reuse") != "none": + raise StudyError("workspace donor cannot claim code reuse") + + capabilities = profile.get("mechanical_capabilities_to_borrow") + if not isinstance(capabilities, list) or len(capabilities) != 22 or len(set(capabilities)) != 22: + raise StudyError("exactly twenty-two unique mechanical capabilities are required") + if set(profile.get("operation_effect_classes", [])) != EXPECTED_EFFECTS: + raise StudyError("operation effect classes mismatch") + if set(profile.get("object_availability_states", [])) != EXPECTED_AVAILABILITY: + raise StudyError("object availability states mismatch") + if set(profile.get("activity_result_states", [])) != EXPECTED_RESULTS: + raise StudyError("Activity result states mismatch") + caller = profile.get("caller_owned_functions") + if not isinstance(caller, list) or len(caller) < 10: + raise StudyError("caller-owned semantic and authority functions are incomplete") + for phrase in ("approval and rejection", "semantic merge and reconciliation", "next-action selection"): + if phrase not in caller: + raise StudyError(f"caller ownership missing: {phrase}") + rejected = profile.get("rejected_patterns") + if not isinstance(rejected, list) or len(rejected) != 8: + raise StudyError("rejected pattern set mismatch") + + gap = load_json(paths["gap"]) + if gap.get("profile_id") != PROFILE_ID: + raise StudyError("gap map profile mismatch") + if gap.get("summary") != EXPECTED_SUMMARY: + raise StudyError("gap map summary mismatch") + if gap.get("frozen_contract_change_required") is not False or gap.get("runtime_implementation_authorized") is not False: + raise StudyError("gap map changes frozen authority") + mappings = gap.get("mappings") + if not isinstance(mappings, list) or len(mappings) != 28: + raise StudyError("exactly twenty-eight gap mappings are required") + names = [item.get("capability") for item in mappings if isinstance(item, dict)] + if len(names) != 28 or len(set(names)) != 28: + raise StudyError("gap capabilities must be unique") + counts = Counter(item.get("classification") for item in mappings) + if dict(counts) != {key: value for key, value in EXPECTED_SUMMARY.items() if value}: + raise StudyError("gap mapping counts do not match summary") + if counts.get("candidate_core_extension", 0) != 0: + raise StudyError("deep study cannot add a Core extension") + for capability in ( + "context selection and relevance", + "tool and Provider selection", + "semantic merge of worker outputs", + "approval decision", + "result acceptance and canonical promotion", + "next-action and schedule-purpose selection", + ): + item = next((entry for entry in mappings if entry.get("capability") == capability), None) + if not isinstance(item, dict) or item.get("classification") != "caller_application_composition": + raise StudyError(f"semantic function is not caller-owned: {capability}") + + fixtures = load_json(paths["fixtures"]) + if fixtures.get("profile_id") != PROFILE_ID: + raise StudyError("fixture profile mismatch") + if fixtures.get("new_core_entity_required") is not False or fixtures.get("frozen_contract_change_required") is not False: + raise StudyError("fixtures reopen frozen contracts") + if fixtures.get("runtime_implementation_authorized") is not False: + raise StudyError("fixtures authorize runtime implementation") + fixture_list = fixtures.get("fixtures") + if not isinstance(fixture_list, list) or len(fixture_list) != 20: + raise StudyError("exactly twenty fixtures are required") + fixture_ids = {item.get("id") for item in fixture_list if isinstance(item, dict)} + if fixture_ids != EXPECTED_FIXTURES: + raise StudyError("fixture identities mismatch") + kinds = Counter(item.get("kind") for item in fixture_list) + if kinds != Counter({"positive": 11, "negative": 9}): + raise StudyError("fixture positive/negative balance mismatch") + for item in fixture_list: + if not isinstance(item.get("proof"), list) or not item["proof"]: + raise StudyError(f"fixture proof missing: {item.get('id')}") + semantic = next(item for item in fixture_list if item.get("id") == "semantic-merge-remains-caller-owned") + if semantic.get("expected") != "retain_both_without_ptah_verdict": + raise StudyError("Ptah must not issue a semantic verdict") + materialization = next(item for item in fixture_list if item.get("id") == "reference-is-not-materialized-by-name") + if materialization.get("expected") != "no_local_path_claim": + raise StudyError("external reference/materialization boundary weakened") + invocation = next(item for item in fixture_list if item.get("id") == "invocation-is-not-success") + if invocation.get("expected") != "not_run_or_unknown_effect_not_success": + raise StudyError("invocation is incorrectly treated as success") + + catalog = load_json(paths["catalog"]) + if catalog.get("catalog_count") != 14 or catalog.get("schema_count") != 346 or catalog.get("state_machine_count") != 99: + raise StudyError("frozen contract catalog changed during donor study") + + report_files = { + name: {"path": str(path.relative_to(root)), "sha256": sha256(path)} + for name, path in paths.items() + } + return { + "schema_version": "0.1.0", + "record_type": "ptah.phase0c.workspace_operations_deep_study_validation", + "status": "pass", + "profile_id": PROFILE_ID, + "study_method": "10 primary + 10 independent verifier lanes", + "mechanical_capability_count": 22, + "gap_mapping_count": 28, + "fixture_count": 20, + "new_core_entity_required": False, + "frozen_contract_change_required": False, + "runtime_implementation_authorized": False, + "files": report_files, + } + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--repo-root", default=".") + parser.add_argument("--output") + args = parser.parse_args() + report = validate(Path(args.repo_root)) + encoded = json.dumps(report, indent=2, sort_keys=True) + "\n" + if args.output: + Path(args.output).write_text(encoded, encoding="utf-8") + print(encoded, end="") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/test_check_workspace_operations_donor_v2.py b/tools/test_check_workspace_operations_donor_v2.py new file mode 100644 index 00000000..49778656 --- /dev/null +++ b/tools/test_check_workspace_operations_donor_v2.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python3 +"""Adversarial regression tests for the deep Workspace operations donor study.""" +from __future__ import annotations + +import json +import shutil +import tempfile +import unittest +from pathlib import Path + +from check_workspace_operations_donor_v2 import StudyError, validate + + +class WorkspaceOperationsStudyTests(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.source_root = Path(__file__).resolve().parents[1] + + def setUp(self) -> None: + self.tempdir = tempfile.TemporaryDirectory() + self.root = Path(self.tempdir.name) / "repo" + shutil.copytree(self.source_root, self.root) + + def tearDown(self) -> None: + self.tempdir.cleanup() + + def load(self, relative: str) -> dict: + return json.loads((self.root / relative).read_text(encoding="utf-8")) + + def save(self, relative: str, value: dict) -> None: + (self.root / relative).write_text(json.dumps(value, indent=2) + "\n", encoding="utf-8") + + def assert_invalid(self) -> None: + with self.assertRaises(StudyError): + validate(self.root) + + def test_00_valid_candidate_passes(self) -> None: + report = validate(self.root) + self.assertEqual(report["status"], "pass") + self.assertEqual(report["mechanical_capability_count"], 22) + self.assertEqual(report["fixture_count"], 20) + self.assertFalse(report["runtime_implementation_authorized"]) + + def test_01_private_source_claim_fails(self) -> None: + path = "design/candidates/workspace-operations-profile-v2.json" + data = self.load(path) + data["source_boundary"]["openai_private_source_used"] = True + self.save(path, data) + self.assert_invalid() + + def test_02_hidden_implementation_claim_fails(self) -> None: + path = "design/candidates/workspace-operations-profile-v2.json" + data = self.load(path) + data["source_boundary"]["hidden_implementation_inferred"] = True + self.save(path, data) + self.assert_invalid() + + def test_03_code_reuse_claim_fails(self) -> None: + path = "design/candidates/workspace-operations-profile-v2.json" + data = self.load(path) + data["source_boundary"]["code_reuse"] = "selective" + self.save(path, data) + self.assert_invalid() + + def test_04_ten_for_two_lane_loss_fails(self) -> None: + path = "design/candidates/workspace-operations-profile-v2.json" + data = self.load(path) + data["study_method"]["independent_verifier_lanes"] = 9 + self.save(path, data) + self.assert_invalid() + + def test_05_decision_authority_fails(self) -> None: + path = "design/candidates/workspace-operations-profile-v2.json" + data = self.load(path) + data["decision_authority"] = True + self.save(path, data) + self.assert_invalid() + + def test_06_context_authority_fails(self) -> None: + path = "design/candidates/workspace-operations-profile-v2.json" + data = self.load(path) + data["context_selection_authority"] = True + self.save(path, data) + self.assert_invalid() + + def test_07_review_authority_fails(self) -> None: + path = "design/candidates/workspace-operations-profile-v2.json" + data = self.load(path) + data["review_authority"] = True + self.save(path, data) + self.assert_invalid() + + def test_08_approval_authority_fails(self) -> None: + path = "design/candidates/workspace-operations-profile-v2.json" + data = self.load(path) + data["approval_authority"] = True + self.save(path, data) + self.assert_invalid() + + def test_09_new_core_entity_fails(self) -> None: + path = "design/candidates/workspace-operations-profile-v2.json" + data = self.load(path) + data["new_core_entity_required"] = True + self.save(path, data) + self.assert_invalid() + + def test_10_frozen_contract_reopen_fails(self) -> None: + path = "design/candidates/workspace-operations-gap-map-v2.json" + data = self.load(path) + data["frozen_contract_change_required"] = True + self.save(path, data) + self.assert_invalid() + + def test_11_runtime_authorization_fails(self) -> None: + path = "design/candidates/workspace-operations-profile-v2.json" + data = self.load(path) + data["runtime_implementation_authorized"] = True + self.save(path, data) + self.assert_invalid() + + def test_12_missing_effect_class_fails(self) -> None: + path = "design/candidates/workspace-operations-profile-v2.json" + data = self.load(path) + data["operation_effect_classes"].remove("destructive") + self.save(path, data) + self.assert_invalid() + + def test_13_missing_materialization_state_fails(self) -> None: + path = "design/candidates/workspace-operations-profile-v2.json" + data = self.load(path) + data["object_availability_states"].remove("external_reference") + self.save(path, data) + self.assert_invalid() + + def test_14_result_state_collapse_fails(self) -> None: + path = "design/candidates/workspace-operations-profile-v2.json" + data = self.load(path) + data["activity_result_states"].remove("declined") + self.save(path, data) + self.assert_invalid() + + def test_15_caller_semantic_merge_ownership_loss_fails(self) -> None: + path = "design/candidates/workspace-operations-profile-v2.json" + data = self.load(path) + data["caller_owned_functions"].remove("semantic merge and reconciliation") + self.save(path, data) + self.assert_invalid() + + def test_16_gap_extension_fails(self) -> None: + path = "design/candidates/workspace-operations-gap-map-v2.json" + data = self.load(path) + data["summary"]["candidate_core_extension"] = 1 + data["summary"]["covered_by_neutral_substrate"] = 15 + data["mappings"][0]["classification"] = "candidate_core_extension" + self.save(path, data) + self.assert_invalid() + + def test_17_context_mapping_to_core_fails(self) -> None: + path = "design/candidates/workspace-operations-gap-map-v2.json" + data = self.load(path) + item = next(x for x in data["mappings"] if x["capability"] == "context selection and relevance") + item["classification"] = "covered_by_neutral_substrate" + data["summary"]["caller_application_composition"] = 5 + data["summary"]["covered_by_neutral_substrate"] = 17 + self.save(path, data) + self.assert_invalid() + + def test_18_approval_mapping_to_core_fails(self) -> None: + path = "design/candidates/workspace-operations-gap-map-v2.json" + data = self.load(path) + item = next(x for x in data["mappings"] if x["capability"] == "approval decision") + item["classification"] = "covered_by_neutral_substrate" + data["summary"]["caller_application_composition"] = 5 + data["summary"]["covered_by_neutral_substrate"] = 17 + self.save(path, data) + self.assert_invalid() + + def test_19_missing_materialization_fixture_fails(self) -> None: + path = "design/candidates/fixtures/workspace-operations-fixtures-v2.json" + data = self.load(path) + item = next(x for x in data["fixtures"] if x["id"] == "reference-is-not-materialized-by-name") + item["expected"] = "invent_local_path" + self.save(path, data) + self.assert_invalid() + + def test_20_invocation_as_success_fails(self) -> None: + path = "design/candidates/fixtures/workspace-operations-fixtures-v2.json" + data = self.load(path) + item = next(x for x in data["fixtures"] if x["id"] == "invocation-is-not-success") + item["expected"] = "succeeded" + self.save(path, data) + self.assert_invalid() + + def test_21_ptah_semantic_verdict_fails(self) -> None: + path = "design/candidates/fixtures/workspace-operations-fixtures-v2.json" + data = self.load(path) + item = next(x for x in data["fixtures"] if x["id"] == "semantic-merge-remains-caller-owned") + item["expected"] = "ptah_selects_winner" + self.save(path, data) + self.assert_invalid() + + def test_22_missing_official_source_fails(self) -> None: + path = self.root / "design/donors/openai-chatgpt-workspace-deep-study.md" + text = path.read_text(encoding="utf-8") + text = text.replace("https://help.openai.com/en/articles/20001247", "https://example.invalid/missing") + path.write_text(text, encoding="utf-8") + self.assert_invalid() + + def test_23_study_contract_reopen_text_loss_fails(self) -> None: + path = self.root / "design/donors/openai-chatgpt-workspace-deep-study.md" + text = path.read_text(encoding="utf-8") + text = text.replace("no reason to reopen WP01–WP14", "contracts may be reopened") + path.write_text(text, encoding="utf-8") + self.assert_invalid() + + def test_24_existing_neutral_profile_drift_fails(self) -> None: + path = "design/candidates/ai-project-workspace-profile.json" + data = self.load(path) + data["ptah_role"] = "intelligent_workspace_coordinator" + self.save(path, data) + self.assert_invalid() + + def test_25_catalog_count_change_fails(self) -> None: + path = "contracts/generated/catalog-index.json" + data = self.load(path) + data["schema_count"] = 347 + self.save(path, data) + self.assert_invalid() + + +if __name__ == "__main__": + unittest.main()