Repository Change Window¶
repository-change-window is a built-in, default-off capability for two
related caller outcomes:
- apply a typed local schedule before repository commits and pushes; and
- retain a restart-safe inventory of unmerged work when that schedule blocks delivery.
The capability owns policy evaluation and pending-change lifecycle. The
bundled git-hook provider owns Git integration. The LoopX Kernel remains the
authority for goals, todos, gates, and quota; installing this provider does not
grant repository or remote-write authority.
Install and inspect¶
Every mutating command previews by default. The built-in schedule shown below
blocks Monday through Friday from 10:00 inclusive until 21:00 exclusive in
Asia/Shanghai:
loopx change-window install --repo-path . --format json
loopx change-window install --repo-path . --execute --format json
loopx change-window status --repo-path . --format json
loopx change-window verify --repo-path . --format json
Before a repository provider exists, status reports one of two typed states:
provider_not_installedwhen neithercore.hooksPathnorcore.sshCommandcontributes an effective configured guard; oreffective_external_guard_detectedwhen either surface is configured.
The external-guard diagnostic contains only surface and configuration-scope enums. It never returns a hook path or command, infers a schedule, or treats an arbitrary script as trusted provider state. A bounded signature can recognize the earlier LoopX global commit-time gate and offer a typed, preview-first repository-provider migration. That preview layers the repository provider, preserves the effective global guard, and does not modify global Git configuration. Unknown external guards remain diagnostic-only and fail closed.
Installation defaults to the backward-compatible hook_only enforcement
level. It manages pre-commit and pre-push; Git's --no-verify option can
skip those client hooks. Repositories that want a stronger local guard must
opt in explicitly:
loopx change-window install \
--repo-path . \
--enforcement-level reference_guard \
--execute \
--format json
loopx change-window verify --repo-path . --format json
reference_guard additionally manages Git's reference-transaction hook and
the repository-local core.sshCommand route. In a blocked window it rejects a
HEAD or local-branch update that introduces a new commit, including
git commit --no-verify. The pre-push hook covers ordinary pushes, while the
SSH route also rejects an SSH push that uses --no-verify. Checkout,
linked-worktree creation, branch deletion, SSH fetches, and local branch
creation at a commit already reachable from another local branch, a remote-tracking
branch, or the current HEAD stays available. This includes fast-forward pulls
after fetch; a pull that creates a new merge/rebase commit still faces the gate.
Tags, notes, and custom refs do not make a new commit
eligible for a local-branch update during the blocked window.
Unknown SSH service commands fail closed. status and verify report the
typed enforcement level, exact managed hook set, and SSH-route health.
Managed hooks use the selected-command CLI loader rather than loading every
capability. Reference updates outside HEAD/local branches and post-transaction
phases do not evaluate the time policy. Provider integrity checks and the prior
hook's phase, input, output and exit status remain in force. Successful managed
hooks are quiet; failures remain visible. Direct change-window hook diagnostics
remain structured unless LOOPX_GIT_HOOK_QUIET_SUCCESS=1 is set. This flag only
controls output, never admission, and is harmless with older runtimes.
After upgrading the runtime, preview change-window install with the existing
policy and enforcement settings plus --replace, then add --execute to refresh
older generated hooks. Preserve the existing window; do not accidentally use
installation defaults as a new policy. Status/verify still recognize an intact
older installation; install detects that its hook generation needs refresh.
Customize one typed v0 window with repeatable weekdays and IANA timezone data:
loopx change-window install \
--repo-path . \
--timezone Europe/Berlin \
--blocked-weekday mon \
--blocked-weekday tue \
--blocked-start 09:30 \
--blocked-end 18:00 \
--execute \
--format json
An end earlier than its start is an overnight window associated with the day on which it starts. Equal start and end values are rejected instead of being interpreted as an implicit full-day lock.
The provider writes only repository-local Git configuration and private state
under the repository's Git common directory. Linked worktrees therefore share
one installation and policy. Managed hooks evaluate the policy and then call
the same hook from the route that was effective before installation, including
stdin and hook arguments. The SSH guard delegates to the previously effective
core.sshCommand, or to ssh when none was configured. A modified managed
command, changed hook or SSH route, invalid policy, or missing state fails
verification closed.
At the CLI composition root, this capability registers a bounded, read-only
interaction_projection hook. Kernel orchestration validates the registration
and candidate in the typed TypeScript boundary, isolates provider failure, and
rejects write scopes or conflicting projection slots without importing this
capability. When the provider is installed and all provider checks pass,
quota should-run adds interaction_contract.repository_delivery. This
projection keeps prepare_dirty_worktree and validate_dirty_worktree
admitted by the local schedule while projecting commit and push separately
from the verified policy decision. A blocked decision carries
next_eligible_at. Uninstalled, external-only, or drifted providers produce no
trusted repository-delivery admission. The projection is path-free, is shared
across linked worktrees, does not extend to separate clones, and never grants
remote-write authority or overrides other LoopX gates.
This is a read-time projection hook, not an effect callback: it receives only the path-free provider status candidate, runs at most once per dispatch, and cannot write Git configuration, create commits, push, or merge. A future post-writeback hook uses a separate durable-receipt and idempotency contract; this registration does not implicitly gain that lifecycle.
The execution clock is not a caller argument. Live hooks use the invocation clock; tests inject an aware fake clock through the Python contract so an artifact timestamp cannot move a commit or push into an allowed window.
Pending-change ledger¶
When a hook blocks a commit or push, it automatically records or refreshes a stable checkout-scoped pending change. Attached branches retain their existing identity; detached worktrees receive a path-free, machine-local checkout id so work prepared during a change window can be recovered without creating a branch. The shared runtime event contains:
- a stable
change_idand credential-free repository identity; - typed
branchordetachedcheckout identity and exact head OID; - counts and content digests for staged, unstaged, and untracked state. An untracked entry git will not hash as an object -- a symlink to a directory, for example -- is digested by what describes it, the link target or a bounded directory inventory, so one such entry cannot fail the whole fingerprint;
- the typed gate decision and next eligible time; and
- lifecycle source and update time.
It does not contain code, a patch, a diff body, credential material, file
names, or a local absolute path. A separate mode-0600 machine-private locator
retains the producing worktree plus its repository-relative changed-path
inventory; list, reconciliation, and verification packets expose only counts
and never expose those paths.
Add goal, Todo, PR, write-scope, and validation lineage explicitly when it is available:
loopx change-window record \
--repo-path . \
--goal-id example-goal \
--todo-id todo_example123 \
--write-scope 'src/**' \
--validation-ref 'pytest:tests/unit:passed' \
--execute \
--format json
loopx change-window list --state open --format json
loopx change-window verify --change-id change_example123 --format json
A hook can only observe an attempted Git operation. Work prepared during a
blocked window may therefore remain dirty without reaching pre-commit,
reference-transaction, or pre-push. Reconcile the current checkout during
normal writeback, or explicitly sweep every linked and detached worktree in
the same Git common directory before a wider handoff:
loopx change-window reconcile --repo-path . --format json
loopx change-window reconcile --repo-path . --execute --format json
loopx change-window reconcile \
--repo-path . \
--all-linked-worktrees \
--execute \
--format json
Reconciliation is a no-op while the policy allows repository changes and
requires an installed provider. The bounded default inspects only
--repo-path; --all-linked-worktrees is the explicit repository-wide
recovery sweep. Both ignore clean worktrees, record dirty checkouts
idempotently, and return only path-free checkout ids and counts. Run the
bounded form after preparing gated work and the wider sweep before a handoff,
shutdown, or gate-open delivery pass. Wide-sweep output keeps aggregate counts
authoritative and caps per-checkout detail so a large historical worktree set
does not flood the control packet.
record is idempotent for the same identity and fingerprint. A changed head,
changed-path inventory, or worktree fingerprint appends a refreshed event
rather than overwriting history. verify distinguishes missing
locator/worktree, repository or checkout mismatch, missing recorded head,
branch or detached-HEAD movement, private changed-path inventory drift, and
worktree-fingerprint drift. Verification reports only inventory counts, never
the private path values.
Close the lifecycle only with a typed outcome and compact public-safe evidence:
loopx change-window resolve \
--change-id change_example123 \
--resolution merged \
--evidence 'github:owner/repo#123' \
--execute \
--format json
The terminal outcomes are merged, superseded, and abandoned.
superseded also requires --superseded-by. Exact retries are idempotent;
conflicting terminal evidence fails closed. Resolution retains the public-safe
event history and removes the no-longer-needed machine-private worktree locator.
Uninstall and rollback¶
Preview first, then remove the managed provider:
loopx change-window uninstall --repo-path . --format json
loopx change-window uninstall --repo-path . --execute --format json
Uninstall restores the exact repository-local core.hooksPath and
core.sshCommand values that preceded installation, or removes either local
override when none existed. It refuses to overwrite drifted provider state.
Pending-change history is not deleted by uninstall; resolve it through the
ledger lifecycle instead.
Authority and enforcement boundary¶
This is local workflow enforcement, not branch protection. hook_only can be
bypassed with --no-verify; reference_guard closes that commit path and the
repository's SSH transport path. HTTPS pushes that both skip pre-push and
avoid SSH, replacing core.hooksPath or core.sshCommand, using an alternate
Git configuration or binary, staging fabricated remote-tracking refs, directly
editing ref files, writing from another
machine, or calling a hosting API remain outside its authority. Use OS policy
plus remote branch protection or server-side controls for a security boundary.
The capability does not push, merge, create a PR, modify protected branches,
or treat schedule admission as permission for any of those effects.