diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 0ccf27e..94b89bc 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -34,27 +34,13 @@ run_local_standard_pin_check() { return 1 fi - # This first boundary reads only staged local manifests and managed digests. - # The separately managed controller may then ask Goal to verify freshness. + # Commits read only staged local manifests and managed digests. Resolve a + # newer release explicitly in its adoption ticket, outside this boundary. python3 "$runner" verify-pin --root "$root" --staged >/dev/null } -run_standard_update_controller() { - local runner="$root/.governance/precommit_standard_update.py" - if [[ ! -f "$runner" ]]; then - if [[ ! -e "$root/.governance/standard-adoption.json" ]]; then - return 0 - fi - echo "GOV-STANDARD-UPDATE-001: the managed standard update controller is missing." >&2 - echo " Restore the pinned package; never bypass the pre-commit freshness boundary." >&2 - return 1 - fi - python3 "$runner" --root "$root" --ticket "$ticket" -} - run_commit_guards() { run_local_standard_pin_check - run_standard_update_controller run_worktree_guard } diff --git a/.governance/error/GOV-STANDARD-UPDATE.md b/.governance/error/GOV-STANDARD-UPDATE.md index 1b1c53c..ba017f7 100644 --- a/.governance/error/GOV-STANDARD-UPDATE.md +++ b/.governance/error/GOV-STANDARD-UPDATE.md @@ -1,16 +1,19 @@ -# GOV-STANDARD-UPDATE-001: pre-commit cannot safely prepare a standard update +# GOV-STANDARD-UPDATE-001: explicit standard update could not complete ## Situation -The managed hook found a pinned adoption but its controller is missing, Goal is -unavailable or incompatible, release verification failed, or Goal prepared or -refused an update and stopped the commit. +The explicitly invoked compatibility updater found Goal unavailable or +incompatible, release verification failed, or Goal prepared or refused an +update. Its legacy `--pre-commit` protocol prepares changes and returns control +for review; the managed commit hook no longer invokes this updater. ## Meaning The committed pin remains authoritative. A newer release gains trust only when Goal verifies its annotated tag, final GitHub Release, full SHA and generated digests. Preparation does not stage, commit, merge or publish the result. +The managed commit hook checks the staged local immutable pin and worktree +guard only. A new upstream release does not change a feature ticket's pin. ## Safe resolution @@ -19,13 +22,15 @@ digests. Preparation does not stage, commit, merge or publish the result. 3. Validate `.governance/standard-adoption.json`; when `executor` is `koru-goal`, install a compatible Koru supervisor as well. 4. Allocate or resume exactly one standard-adoption ticket in its own worktree. -5. Retry the commit, review the prepared diff, stage it explicitly and retry. +5. Run explicit adoption in that ticket, review the prepared diff, validate it + and stage it explicitly before committing. ## Verification -- The Goal pre-commit adoption command returns zero when the verified release +- The explicitly invoked Goal preparation command returns zero when the verified release is already pinned. -- A prepared update remains visible and the original commit remains uncreated. +- A prepared update remains visible for review and does not create a commit. +- An ordinary commit does not invoke Goal, Koru or release discovery. - Managed governance and standard conformance pass after explicit restaging. ## Do not diff --git a/.governance/manifest.base.json b/.governance/manifest.base.json index b2ee268..596f1ed 100644 --- a/.governance/manifest.base.json +++ b/.governance/manifest.base.json @@ -110,7 +110,7 @@ "stacks": [], "standard": { "id": "wellmanifest/new-project", - "version": "0.20.11" + "version": "0.20.12" }, "ticket": { "activeStatuses": [ diff --git a/.governance/manifest.json b/.governance/manifest.json index 55aadb5..8ac4b17 100644 --- a/.governance/manifest.json +++ b/.governance/manifest.json @@ -194,7 +194,7 @@ ], "standard": { "id": "wellmanifest/new-project", - "version": "0.20.11" + "version": "0.20.12" }, "ticket": { "activeStatuses": [ diff --git a/.governance/manifest.lock.json b/.governance/manifest.lock.json index 5b7622d..8b3de96 100644 --- a/.governance/manifest.lock.json +++ b/.governance/manifest.lock.json @@ -2,7 +2,7 @@ "managedFiles": { ".aider.conf.yml": "756af4477d7a0d39361919c957ee954807259f15cabf4dcabde81677eac6efd6", ".cursor/rules/new-project-standard.mdc": "72699a5cb3718be9a50603eda9c8a6d960f21b1949fdfe2b35013385b22f9afe", - ".githooks/pre-commit": "d6dc5c9dddb9f8b59f235a11d95a384fe4fcf2f2f96efdb63e127a76a85f2681", + ".githooks/pre-commit": "8299a163efdb520df8726769a7f59a3bc011d8581a7a028e40e4e3c0eb7f52d4", ".github/copilot-instructions.md": "b31f9ba957ce108a83851f48f8dbff8b4301d1c744a77fe76914e6bb8499aca3", ".github/workflows/new-project-governance.yml": "cc567c87a3a256a3975d93625a575ec6b03cbc4f1b12180abf61f97df35f7de2", ".governance/AGENT_DECISIONS.md": "fb8fcbb00ba4100ec230aea34ef4634852dae30929eb9eaeafe548fbc7b1de87", @@ -30,7 +30,7 @@ ".governance/error/GOV-INTENT.md": "4dc29dbf4c39d18cd11a5ec1eccc257a06d2bea2f95b560aa58085672611d4a9", ".governance/error/GOV-PACKAGING.md": "4a602c65a655e9b8b631487fa24982df00e0ae874cf5bdb5129f6493bb440c89", ".governance/error/GOV-REMEDIATION-INTENT.md": "ff0f41bf5112a1808738554b51c13a24ce97f7842304f1aca33e9f9846d73aa3", - ".governance/error/GOV-STANDARD-UPDATE.md": "212039b1a5fbe8b61cdb0b35ca1cf3169187a7005123051468962ff8a3a848fa", + ".governance/error/GOV-STANDARD-UPDATE.md": "4a6c83617dc5308d77a1adb1c79f54ec085f280aa3acc33a6e5329a4aa80109c", ".governance/error/GOV-TICKET-001.md": "61e110b93b6111fde243e538e4f34330df36ae29f5a65fb4f90b91d44c1b801e", ".governance/error/GOV-TICKET-ACTIVITY.md": "f1bc9cab86c36028eed449f1c40a229131cb49141cbea35620580e2ef2d2b642", ".governance/error/GOV-TICKET-ALLOCATION.md": "09f92cac24bbbbe5c2967221497fb6b68b02bcd3bf4f56afe36d36ac7d7b0a58", @@ -42,7 +42,7 @@ ".governance/governance_check.py": "305fc2fbf1c01a8a05e514346aa244977efabedd348379ebffd7e6562949fa17", ".governance/intent.schema.json": "9755f20cd189efe2205629781a6cf6b613bd93e9368416793c4de53256b0a9c0", ".governance/lock.schema.json": "ad80c98f800a4a3310870336dcdaf0aa689cc4988f71084d25d76bea2df1242f", - ".governance/manifest.base.json": "e49a636499e17b46b399ebb0b0329df5ea79ed1d98f4264f21b666c732925d33", + ".governance/manifest.base.json": "1a834c8de47b3a03eeece19c686966bce6ea5d9fc6a720305dba4ec70d243d8b", ".governance/manifest.schema.json": "5aa2ccd3f6898834d4e39a78342448145490be56aa132e16ac7c9d64acef8f73", ".governance/package-manifest.json": "a2ea77aca16d59282e1b60105521270ae112ebc52ddb3e08af3eeec26942dc3f", ".governance/precommit_standard_update.py": "c91e2bf9ae9d6ccc77bce0e61450c818a5961edee5bfde3b60426da88e296b0f", @@ -69,7 +69,7 @@ ".governance/work_continuity.py": "2efb720b530cc295b45413a2bc7384a426bd29b5d9b20452035e5943294c18e4", ".governance/workspace_lifecycle_check.py": "f196f33c4884120ca216e65473f32b163f6c6d262624309cf0cbdc37063d181f", ".governance/worktree_guard.py": "b154f6e67626770ec11c9544d31a27b32d9c215ef73ffc9eb8f52e9c3a9b051b", - ".governance/worktree_overlap_check.py": "86336eef38cfd1c2d421052e089e0523643d48ffa1c6fb7437b69cb78e17c785", + ".governance/worktree_overlap_check.py": "dfb6463889617352578576ad699f8baa4246c013cf943e1b427a76176d7e19a5", ".governance/worktree_path_check.py": "d3309b76e2c91dd7f046b00322ec8cb48ef744b8b7908d70e475fedf95e5e196", ".governance/worktrees.lock.json": "9ed607dd339fd4ed30263d7de62231249447568e8e35c8b19fdc0231fca39fde", ".governance/worktrees.schema.json": "9cc10d126e06cafc87cc117f11b8b095676f461de4d168d2a1c2bb959f0fccd5", @@ -92,7 +92,7 @@ "id": "wellmanifest/new-project", "publicationStatus": "published", "sourceRepository": "wellmanifest/new-project", - "sourceRevision": "2cd39286dc0931870a8a60ee11c96dd6448b39e6", - "version": "0.20.11" + "sourceRevision": "bf3099667babd14ee778917d911d6c6bad45dcab", + "version": "0.20.12" } } diff --git a/.governance/worktree_overlap_check.py b/.governance/worktree_overlap_check.py index 1a84603..0be2b0a 100755 --- a/.governance/worktree_overlap_check.py +++ b/.governance/worktree_overlap_check.py @@ -440,6 +440,43 @@ def merge_tree_conflicts(path: Path, left: str, right: str) -> tuple[str, ...] | return tuple(sorted(set(conflicted))) +def pending_main_imports(path: Path) -> set[str]: + """Clean staged imports from the current origin default branch, if proven. + + An unfinished merge exposes already integrated main content as index edits. + It is not a competing contribution. Keep reporting that dirty state, but + exclude it from overlap attribution only when all local Git reads agree. + No fetch or index mutation is needed; unknown or older merge heads retain + conservative behavior. Committed feature edits are never exempted. + """ + try: + incoming = run_git(path, "rev-parse", "--verify", "MERGE_HEAD") + merge_file = Path(run_git(path, "rev-parse", "--path-format=absolute", "--git-path", "MERGE_HEAD")) + if merge_file.read_text(encoding="ascii").splitlines() != [incoming]: + return set() # Octopus merges have more than one source of edits. + remote = run_git(path, "rev-parse", "--verify", + f"refs/remotes/origin/{default_branch(path)}") + if not re.fullmatch(r"[0-9a-f]{40}|[0-9a-f]{64}", incoming) or incoming != remote: + return set() + base = run_git(path, "merge-base", "HEAD", incoming) + + def names(*args: str) -> set[str]: + return set(run_git(path, *args).split("\0")) - {""} + + staged = names("diff", "--cached", "--name-only", "--no-renames", "-z", "HEAD") + different = names("diff", "--cached", "--name-only", "--no-renames", "-z", incoming) + unstaged = names("diff", "--name-only", "--no-renames", "-z") + untracked = names("ls-files", "--others", "--exclude-standard", "-z") + local_commits = names("diff", "--name-only", "--no-renames", "-z", base, "HEAD") + # diff --cached includes unresolved paths; retain an explicit check so + # equality can never be inferred from a non-stage-zero index entry. + unresolved = {entry.split("\t", 1)[1] + for entry in names("ls-files", "--unmerged", "-z")} + return staged - different - unstaged - untracked - local_commits - unresolved + except (AuditError, IndexError, OSError, UnicodeError): + return set() + + def contested_paths( first: "Checkout", second: "Checkout", ignore: tuple[str, ...] ) -> tuple[str, ...]: @@ -450,6 +487,8 @@ def contested_paths( snapshot at the same HEAD contributes no competing committed change. Unknown ancestry retains the conservative path-intersection fallback. """ + first_dirty = set(first.dirty_paths) - pending_main_imports(first.path) + second_dirty = set(second.dirty_paths) - pending_main_imports(second.path) first_changes, second_changes = set(first.changed_paths), set(second.changed_paths) if first.head and second.head: base = first.head if first.head == second.head else merge_base(first.path, first.head, second.head) @@ -459,11 +498,11 @@ def contested_paths( try: first_committed = set(run_git(first.path, "diff", "--name-only", base, first.head).splitlines()) second_committed = set(run_git(second.path, "diff", "--name-only", base, second.head).splitlines()) - first_changes = first_committed | set(first.dirty_paths) - second_changes = second_committed | set(second.dirty_paths) + first_changes = first_committed | first_dirty + second_changes = second_committed | second_dirty except AuditError: pass - dirty_overlap = (set(first.dirty_paths) & second_changes) | (set(second.dirty_paths) & first_changes) + dirty_overlap = (first_dirty & second_changes) | (second_dirty & first_changes) conflicts: set[str] = set() if first.head and second.head and first.head != second.head: if not is_ancestor(first.path, first.head, second.head) and not is_ancestor( diff --git a/project/ticket-091/README.md b/project/ticket-091/README.md new file mode 100644 index 0000000..bb25b6b --- /dev/null +++ b/project/ticket-091/README.md @@ -0,0 +1,13 @@ +# Ticket 091: Immutable pin and main import fix + +- **Status**: IN_PROGRESS +- **Workflow state**: PUBLICATION +- **Owner**: codex + +SESSION_EXECUTION_AUTHORIZATION: User requested continued Semcod publication. The existing hook blocks PR 124 pending standard freshness; its runbook requires a separate managed adoption ticket. Adopt the already published fix without bypassing the hook. + +## Acceptance criteria +- [x] AC-01: Adopt immutable bf3099667babd14ee778917d911d6c6bad45dcab, retaining target settings and package bindings. +- [ ] AC-02: Managed checks and Python CI matrix pass before independent protected publication. + +Validation: 723 tests passed, 2 skipped; managed governance and Docker Compose passed. Required Python matrix and independent publication remain pending. diff --git a/project/ticket-091/intent.json b/project/ticket-091/intent.json new file mode 100644 index 0000000..b963cbd --- /dev/null +++ b/project/ticket-091/intent.json @@ -0,0 +1,86 @@ +{ + "schema": "new-project.intent/v3", + "ticket": "ticket-091", + "summary": "Adopt published immutable pin and main import governance fix", + "workstream": "governance", + "classification": { + "kind": "BUG", + "priority": "P1", + "origin": "requested" + }, + "allowedPaths": [ + ".githooks/pre-commit", + ".governance/error/GOV-STANDARD-UPDATE.md", + ".governance/manifest.base.json", + ".governance/manifest.json", + ".governance/manifest.lock.json", + ".governance/worktree_overlap_check.py", + "pyproject.toml", + "project/ticket-091/**" + ], + "forbiddenPaths": [ + "project/ticket-*/user-*.md" + ], + "stacks": [], + "dependsOn": [], + "conflictsWith": [], + "integrationTicket": null, + "delivery": { + "acceptedBaseSha": "b3abc2660105a82050c61e7ea55b4381d391e881", + "targetBranch": "main", + "outcome": "Adopt published new-project 0.20.12 to preserve immutable pins and correctly recognize imports from main, unblocking ticket-090.", + "nonGoals": [ + "No product source, action dependencies or protected-check changes" + ], + "complexity": "M", + "estimatedMinutes": 60, + "budgets": { + "maxImplementationFiles": 9, + "maxAffectedComponents": 2, + "maxPublicInterfaceChanges": 0, + "maxRuntimeDependencies": 0 + }, + "architecture": { + "status": "accepted", + "decision": "Use the managed immutable standard updater, retaining target-owned manifest settings and package bindings.", + "components": [ + { + "name": "governance", + "paths": [ + ".githooks/pre-commit", + ".governance/error/GOV-STANDARD-UPDATE.md", + ".governance/manifest.base.json", + ".governance/manifest.json", + ".governance/manifest.lock.json", + ".governance/worktree_overlap_check.py", + "pyproject.toml" + ] + } + ], + "responsibilityChanges": false, + "interfaceChanges": [], + "dataChanges": [], + "ui": { + "impact": "none", + "states": [], + "evidence": [] + }, + "rollback": "Review a subsequent immutable standard update; preserve previous pins in history." + }, + "runtimeDependencies": [], + "standardAdoption": { + "sourceRepository": "wellmanifest/new-project", + "fromRevision": "2cd39286dc0931870a8a60ee11c96dd6448b39e6", + "toRevision": "bf3099667babd14ee778917d911d6c6bad45dcab" + }, + "validation": [ + { + "criterion": "AC-01", + "commands": [ + "./project/governance-check.sh --actor agent" + ], + "evidence": "receipt:local:ticket-089-local-ci-adoption" + } + ] + } +} diff --git a/pyproject.toml b/pyproject.toml index 441a43a..1ec0256 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,8 +71,8 @@ python_files = ["test_*.py"] addopts = "-p wellmanifest_governance" [tool.wellmanifest] -standard = "0.20.11" -revision = "2cd39286dc0931870a8a60ee11c96dd6448b39e6" +standard = "0.20.12" +revision = "bf3099667babd14ee778917d911d6c6bad45dcab" gate = "project/governance-check.sh" [tool.pfix]