Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ Versions follow [Semantic Versioning](https://semver.org/).

### Fixed

- **Operation state writes now participate in writer-boundary parity
(CC-508).** `layout.yaml` declares the three operation writer entry points
and the append-locked `children.jsonl` relation, while dropping the stale,
unimplemented `review_upsert` declaration. The production-domain ratchet now
recognizes operation projections and child relations as load-bearing state,
and the affected-test planner selects layout parity whenever either the
layout contract or state writer changes.

- **A detached gate launcher failure terminalizes its childless parent
(CC-508).** `pmctl gate run` creates the parent operation before either
lifecycle path starts, but the detached branch returned the launcher's status
Expand Down
20 changes: 16 additions & 4 deletions core/state/layout.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
# 3. store_root_default below (fallback)
# $PROJECT := sha1(git rev-parse --show-toplevel) — partitioning key
# $TASK_ID := pattern ^[A-Z]{1,4}-[0-9]+$ (task.schema.json)
# $TS_HEX := YYYYMMDDTHHMMSSZ-<6 hex> (run/event/review id suffix)
# $OPERATION_ID := pattern ^op-[0-9]{8}T[0-9]{6}Z-[a-f0-9]{6}$
# $TS_HEX := YYYYMMDDTHHMMSSZ-<6 hex> (run/event/review/operation id suffix)
# $YYYYMM := archive rotation period (UTC month)
# $NNNN := zero-padded monotonic archive segment within $YYYYMM

Expand Down Expand Up @@ -100,7 +101,14 @@ project_partition:
file_pattern: "op-$TS_HEX.json"
schema: ../schema/operation.schema.json
description: "Parent-operation lifecycle records and append-only child relations."
write_strategy: write-temp-then-rename
write_strategies:
create: write-temp-then-hard-link-exclusive
update: write-temp-then-rename
child_relation:
path_pattern: "$OPERATION_ID/children.jsonl"
format: jsonl
write_strategy: append-locked
lock_base_pattern: "$OPERATION_ID/children"

- path: "context-packs/"
file_pattern: "$TASK_ID-$TS_HEX.json"
Expand All @@ -117,13 +125,17 @@ project_partition:

# Writer-boundary contract
writer:
description: "Single designated write boundary. The runtime layer owns the concrete module binding; only the designated writer module may write to $STORE paths."
description: "Single designated write boundary. The runtime layer owns the concrete module binding; only the designated writer module may write to $STORE paths. entry_points enumerates every public mutating API in that module."
entry_points:
- state_project_identity_write
- state_store_init
- runs_append
- events_append
- operation_upsert
- operation_create
- operation_child_append
- task_upsert
- task_delete
- review_upsert
- decision_upsert
- decision_delete

Expand Down
5 changes: 4 additions & 1 deletion tests/bin/run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,10 @@ map_path() {
add_suite test-pmctl-adapter-generate; add_suite test-pmctl-artifacts
add_suite test-pmctl-gate; add_suite test-pmctl-discovery; behavioral=1 ;;
runtime/lib/state-writer.sh)
add_suite test-state-store; behavioral=1 ;;
add_suite test-state-store; add_suite test-state-layout-parity
add_suite test-pmctl-operation; behavioral=1 ;;
core/state/layout.yaml)
add_suite test-state-store; add_suite test-state-layout-parity; behavioral=1 ;;
runtime/lib/state-paths.sh)
add_suite test-state-paths; add_suite test-state-store-rotation; behavioral=1 ;;
runtime/lib/pmctl-policy.sh)
Expand Down
73 changes: 69 additions & 4 deletions tests/shell/test-layer-boundaries.sh
Original file line number Diff line number Diff line change
Expand Up @@ -142,13 +142,15 @@ check_production_no_direct_state_writes() {
|| s ~ /(^|[^<])>>?/
}
function load_bearing_target(s) {
return s ~ /(runs[.]jsonl|events[.]jsonl|repo[.]json|runs[.]lock|events[.]lock)/ \
|| s ~ /\/(tasks|reviews|decisions|context-packs|archive)(\/|["'\''${}[:space:]])/ \
return s ~ /(runs[.]jsonl|events[.]jsonl|children[.](jsonl|lock|lockdir)|repo[.]json|runs[.]lock|events[.]lock)/ \
|| s ~ /(op-[0-9]{8}T[0-9]{6}Z-[a-f0-9]{6}|[$]operation_id|[$][{]operation_id[}])[.]json/ \
|| s ~ /(^|[\/"'\''[:space:]])(tasks|reviews|decisions|operations|context-packs|archive)(\/|["'\''${}[:space:]])/ \
|| s ~ /\/VERSION(["'\''${}[:space:]]|$)/
}
function state_scope(s) {
return s ~ /(PM_DISPATCH_STATE_ROOT|_sw_store_root|_sw_project_dir|\/projects\/)/ \
|| s ~ /[$][{]?(store_root|proj_dir|version_file|runs_file|events_file|task_file|review_file|decision_file|context_pack[^}]*)[}]?/
|| s ~ /[$][{](store_root|proj_dir|version_file|runs_file|events_file|task_file|review_file|decision_file|operation_file|operation_dir|op_dir|operation_children_file|context_pack[A-Za-z0-9_]*)([^A-Za-z0-9_]|$)/ \
|| s ~ /[$](store_root|proj_dir|version_file|runs_file|events_file|task_file|review_file|decision_file|operation_file|operation_dir|op_dir|operation_children_file|context_pack[A-Za-z0-9_]*)([^A-Za-z0-9_]|$)/
}
function inspect_line(raw, line_number, line, load_bearing, scoped) {
line = raw
Expand Down Expand Up @@ -340,6 +342,69 @@ if should_run "selftest/production_state_writer catches mv destination"; then
rm -rf "$FIX"
fi

if should_run "selftest/production_state_writer catches operation projection variable"; then
name="selftest/production_state_writer catches operation projection variable"
_fixture
printf '#!/usr/bin/env bash\nmv -f "$tmp" "$operation_file"\n' \
> "$FIX/runtime/lib/direct-operation.sh"
_expect_fires "$name" "$(check_production_no_direct_state_writes "$FIX")"
rm -rf "$FIX"
fi

if should_run "selftest/production_state_writer catches operation child append"; then
name="selftest/production_state_writer catches operation child append"
_fixture
printf '#!/usr/bin/env bash\nprintf "%%s\\n" "$child" >> "$entity_root/operations/$operation_id/children.jsonl"\n' \
> "$FIX/runtime/lib/direct-operation-child.sh"
_expect_fires "$name" "$(check_production_no_direct_state_writes "$FIX")"
rm -rf "$FIX"
fi

if should_run "selftest/production_state_writer catches split-line relative child append"; then
name="selftest/production_state_writer catches split-line relative child append"
_fixture
printf '#!/usr/bin/env bash\ncd "$op_dir"\nprintf "%%s\\n" "$child" >> children.jsonl\n' \
> "$FIX/runtime/lib/direct-relative-operation-child.sh"
_expect_fires "$name" "$(check_production_no_direct_state_writes "$FIX")"
rm -rf "$FIX"
fi

if should_run "selftest/production_state_writer catches relative operation projection"; then
name="selftest/production_state_writer catches relative operation projection"
_fixture
printf '#!/usr/bin/env bash\nmv -f "$tmp" "operations/$operation_id.json"\n' \
> "$FIX/runtime/lib/direct-relative-operation.sh"
_expect_fires "$name" "$(check_production_no_direct_state_writes "$FIX")"
rm -rf "$FIX"
fi

if should_run "selftest/production_state_writer catches split-line operation projection"; then
name="selftest/production_state_writer catches split-line operation projection"
_fixture
printf '#!/usr/bin/env bash\ncd "$op_dir"\nmv -f "$tmp" "$operation_id.json"\n' \
> "$FIX/runtime/lib/direct-split-operation.sh"
_expect_fires "$name" "$(check_production_no_direct_state_writes "$FIX")"
rm -rf "$FIX"
fi

if should_run "selftest/production_state_writer catches operation directory variable"; then
name="selftest/production_state_writer catches operation directory variable"
_fixture
printf '#!/usr/bin/env bash\nmkdir -p "$op_dir"\n' \
> "$FIX/runtime/lib/direct-operation-dir.sh"
_expect_fires "$name" "$(check_production_no_direct_state_writes "$FIX")"
rm -rf "$FIX"
fi

if should_run "selftest/production_state_writer does NOT match longer operation variable"; then
name="selftest/production_state_writer does NOT match longer operation variable"
_fixture
printf '#!/usr/bin/env bash\nmkdir -p "$op_directory_cache"\n' \
> "$FIX/runtime/lib/unrelated-operation-cache.sh"
_expect_clean "$name" "$(check_production_no_direct_state_writes "$FIX")"
rm -rf "$FIX"
fi

if should_run "selftest/production_state_writer catches multiline mv destination"; then
name="selftest/production_state_writer catches multiline mv destination"
_fixture
Expand Down Expand Up @@ -381,7 +446,7 @@ fi
if should_run "selftest/production_state_writer allows state readers"; then
name="selftest/production_state_writer allows state readers"
_fixture
printf '#!/usr/bin/env bash\njq -c . "$proj_dir/tasks/CC-1.json"\n' \
printf '#!/usr/bin/env bash\njq -c . "$operation_file"\n' \
> "$FIX/runtime/lib/state-reader.sh"
_expect_clean "$name" "$(check_production_no_direct_state_writes "$FIX")"
rm -rf "$FIX"
Expand Down
36 changes: 34 additions & 2 deletions tests/shell/test-run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ make_fixture() {
local name="$1" repo
repo="$TMP_ROOT/$name"
mkdir -p "$repo/scripts/lib" "$repo/runtime/lib" "$repo/tests/bin" "$repo/tests/lib" \
"$repo/core/schema"
"$repo/core/schema" "$repo/core/state"
cp "$REPO_ROOT/tests/bin/run-tests.sh" "$repo/tests/bin/run-tests.sh"
cp "$REPO_ROOT/tests/bin/run-all-tests.sh" "$repo/tests/bin/run-all-tests.sh"
cp "$SCRIPT_DIR/../lib/test-result.sh" "$repo/tests/lib/test-result.sh"
Expand All @@ -28,7 +28,7 @@ make_fixture() {
cat > "$repo/tests/lib/test-suite-runner.sh" <<'RUNNER'
#!/usr/bin/env bash
set -euo pipefail
suites=(lint-agents lint-scripts lint-script-domain-inventory lint-portable-repo-paths test-lint-shellcheck test-script-domain-inventory test-lint-portable-repo-paths test-lint-frontmatter test-commands test-check-docs-freshness test-guards test-migrate test-install test-doctor test-pmctl-dispatch test-pmctl-context test-pmctl-memory test-pr-gate test-host-manifest test-host-write-codex test-codex-dispatch-continuation test-host-write-parity test-core-schemas test-layer-boundaries test-pm-scripts test-run-tests)
suites=(lint-agents lint-scripts lint-script-domain-inventory lint-portable-repo-paths test-lint-shellcheck test-script-domain-inventory test-lint-portable-repo-paths test-lint-frontmatter test-commands test-check-docs-freshness test-guards test-migrate test-install test-doctor test-pmctl-dispatch test-pmctl-context test-pmctl-memory test-pr-gate test-pmctl-operation test-host-manifest test-host-write-codex test-codex-dispatch-continuation test-host-write-parity test-core-schemas test-layer-boundaries test-pm-scripts test-run-tests test-state-store test-state-layout-parity)
for arg in "$@"; do
if [[ "$arg" == --list ]]; then printf '%s\n' "${suites[@]}"; exit 0; fi
done
Expand Down Expand Up @@ -94,6 +94,36 @@ case_memory_config_mapping_has_no_gap() {
fi
}

case_state_writer_mapping_runs_operation_parity() {
local name=state-writer-mapping-runs-operation-parity repo out status=0 args
args="$TMP_ROOT/$name.args"
repo="$(make_fixture "$name")"
out=$(RUN_TESTS_ARGS_LOG="$args" "$repo/tests/bin/run-tests.sh" \
--path runtime/lib/state-writer.sh --list 2>&1) || status=$?
if [[ "$status" -eq 0 && "$out" == *"test-state-store"* &&
"$out" == *"test-state-layout-parity"* && "$out" == *"test-pmctl-operation"* &&
"$out" != *"coverage gaps"* && ! -e "$args" ]]; then
pass "$name"
else
fail "$name" "status=$status out=$out executed=$([[ -e "$args" ]] && echo yes || echo no)"
fi
}

case_state_layout_mapping_runs_parity() {
local name=state-layout-mapping-runs-parity repo out status=0 args
args="$TMP_ROOT/$name.args"
repo="$(make_fixture "$name")"
out=$(RUN_TESTS_ARGS_LOG="$args" "$repo/tests/bin/run-tests.sh" \
--path core/state/layout.yaml --list 2>&1) || status=$?
if [[ "$status" -eq 0 && "$out" == *"test-state-store"* &&
"$out" == *"test-state-layout-parity"* && "$out" != *"coverage gaps"* &&
! -e "$args" ]]; then
pass "$name"
else
fail "$name" "status=$status out=$out executed=$([[ -e "$args" ]] && echo yes || echo no)"
fi
}

case_docs_mapping_list_only() {
local name=docs-mapping-list-only repo out status=0 args
args="$TMP_ROOT/$name.args"
Expand Down Expand Up @@ -362,6 +392,8 @@ case_invalid_structured_sink_fails_closed() {

case_direct_library_mapping
case_memory_config_mapping_has_no_gap
case_state_writer_mapping_runs_operation_parity
case_state_layout_mapping_runs_parity
case_docs_mapping_list_only
case_operational_docs_map_to_stale_reference_lint
case_agent_mapping_uses_registered_frontmatter_suite
Expand Down
Loading