Skip to content

Enforce immediate prior weekly provenance - #37

Closed
Pigbibi wants to merge 2 commits into
mainfrom
feat/pert-weekly-immediate-prior-reslice
Closed

Enforce immediate prior weekly provenance#37
Pigbibi wants to merge 2 commits into
mainfrom
feat/pert-weekly-immediate-prior-reslice

Conversation

@Pigbibi

@Pigbibi Pigbibi commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • enforce the run-relative immediately prior complete UTC ISO week for schedule and manual dispatch
  • reject arbitrary historical/current/future manual periods before fetch and live publication
  • add a pure five-file weekly artifact builder with source snapshot digest and exact readback validation
  • keep legacy RSS artifact upload before the dedicated weekly artifact

Contract

run.created_at from the trusted GitHub REST workflow-run payload is the period authority. Manual period_start/as_of are required and must exactly match the derived previous week. No wall-clock/latest-RSS fallback or history backfill is supported. Historical backfill requires a separate immutable source snapshot contract.

The dedicated artifact remains political-event-weekly-v1, retention 30 days, with exactly:
period_lock.json, political_events.csv, political_watchlist.csv, political_event_weekly.json, weekly_manifest.json.

Source events are strictly date-parsed and filtered to the locked period; zero-event weeks are valid; incomplete/failed/stale/missing feed status fails closed. Main-only workflow identity and actions:read are retained.

Source design: /Users/lisiyi/Documents/Codex/2026-07-16/pert_weekly_history_backfill_provenance_reslice_design_v1_2026-07-16.md

Validation

  • python3 -m pytest -q — 124 passed
  • uv sync --locked --extra test && uv run pytest -q — 124 passed
  • python3 -m compileall -q src scripts tests — passed
  • actionlint .github/workflows/rss_source_pipeline.yml — passed
  • git diff --check — passed
  • ruff unavailable in environment

Scope exclusions

No history backfill, QAR consumer, Pages/publisher, monthly support, permission expansion, legacy migration, or production trading logic.

Co-Authored-By: Codex <noreply@openai.com>
@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

🤖 Codex PR Review

🚫 Merge blocked: 3 serious issue(s) found in high-risk files

⚖️ Codex Review Arbitration

🚫 block: 至少两条当前 blocking finding 仍被累计 diff 直接证实:1) .github/workflows/rss_source_pipeline.yml 仍以 actions/checkoutref: ${{ github.sha }} 检出,发布步骤仍执行无 refspec 的裸 git push;在 detached HEAD 下这仍会让 COMMIT_OUTPUTS=true 的发布路径失败。该点与上一轮 finding 要求的修复行为一致,不构成 contract conflict。2) 工作流仍在 --continue-on-feed-error 抓取后,先把 data/output/rss_source_pipeline/* 复制到 data/live/* 并提交推送,随后才运行 build_weekly_artifact.py;而 src/political_event_tracking_research/weekly_artifact.py_status() 明确要求 complete=true 且 failed/stale/missing 计数全为 0,否则抛出 feed_status_incomplete。因此抓取失败时,live 数据仍可能先被发布,之后周产物步骤才失败,fail-closed 合同仍被破坏。另,周产物步骤仍直接接受 github.event.inputs.watchlist_path,而 boundary guard 只校验并输出 period/provenance 字段,没有把这些手工输入路径锁定到官方仓库路径,当前关于可替换输入的逻辑风险也未被消除。

🚫 Blocking Issues

These issues must be fixed before this PR can be merged:

1. 🟠 [HIGH] Logic in .github/workflows/rss_source_pipeline.yml

The workflow commits and pushes data/live/* before any step checks that source_fetch_status.json is complete. Because fetch still runs with --continue-on-feed-error, a run with failed/stale feeds will copy partial CSVs into data/live and can push them to main, only for build_weekly_artifact.py to fail later on feed_status_incomplete. That violates the new fail-closed contract and can publish corrupted production data. (line 137)

Suggestion: Gate live publication on a completeness check before any cp/git add, or move the weekly-artifact/feed-status validation ahead of the live commit step and only publish when that validation succeeds.

2. 🟠 [HIGH] Reliability in .github/workflows/rss_source_pipeline.yml

actions/checkout now checks out ref: ${{ github.sha }}, which leaves the repository on a detached HEAD for schedule/workflow_dispatch runs. The later plain git push has no branch destination in that state, so any run with COMMIT_OUTPUTS=true will fail to publish the live CSV commit. (line 117)

Suggestion: Check out refs/heads/main (or ${{ github.ref }} after the main-branch guard) instead of the raw SHA, or keep the detached checkout but push explicitly with a refspec such as git push origin HEAD:main.

3. 🟠 [HIGH] Logic in .github/workflows/rss_source_pipeline.yml

The new boundary guard only constrains period_start/as_of; feeds_path, aliases_path, and watchlist_path are still taken directly from manual dispatch inputs. A manual caller can therefore build a supposedly official political-event-weekly-v1 artifact from alternate feed lists, alias maps, or stale watchlist files while still passing the new provenance checks. (line 128)

Suggestion: For official runs, remove those path inputs or validate them in validate-run-boundary against one canonical allowlist of repo paths before any fetch/build step runs.


Review by Codex PR Review bot • PR

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7c551e647d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +116 to +117
if not isinstance(value, Mapping) or not required.issubset(value) or set(value) - required - optional:
raise _fail("feed_status_invalid")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Accept the status JSON produced by the fetcher

When the weekly artifact step consumes data/output/rss_source_pipeline/source_fetch_status.json, this check rejects the actual payload from write_fetch_status(), which always includes extra top-level keys such as generated_at, item_count, and feeds. As a result, even a successful fetch run raises feed_status_invalid before uploading political-event-weekly-v1, so the new weekly artifact cannot be produced from the workflow's own inputs.

Useful? React with 👍 / 👎.

Comment on lines +126 to +127
ref: ${{ github.sha }}
persist-credentials: false

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve a pushable checkout for live-output commits

When COMMIT_OUTPUTS=true and generated files change, the later publish step runs git commit followed by plain git push; checking out the raw ${{ github.sha }} leaves the job on a detached commit, and persist-credentials: false opts out of the checkout token that actions/checkout documents as enabling later authenticated git commands. Scheduled runs therefore fail instead of publishing live CSV updates whenever there is a change to commit.

Useful? React with 👍 / 👎.

"source_watchlist_length": len(watchlist), "source_watchlist_sha256": _sha(watchlist),
"source_inputs": [{"name": "source_events.csv", "length": len(source_events), "sha256": _sha(source_events)}, {"name": WATCHLIST, "length": len(watchlist), "sha256": _sha(watchlist)}],
}
files: dict[str, bytes] = {LOCK: _json(lock, "period_lock_invalid"), EVENTS: filtered, WATCHLIST: watchlist, WEEKLY: _json(weekly, "weekly_contract_invalid")}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Emit files that match their declared contracts

When a consumer validates the uploaded JSON files with the repository's existing contract parsers, both fail: political_event_weekly.json advertises political_event_weekly.v1 but lacks the required source_artifacts, and period_lock.json advertises pert.weekly.period_lock.v1 but lacks required lock fields such as source_snapshot_id/source_artifacts. Since this line serializes those ad-hoc dicts into the artifact, the new artifact is incompatible with the repo's own validators (contract_shape_invalid / period_lock_shape_invalid).

Useful? React with 👍 / 👎.

run: |
set -euo pipefail
mkdir -p data/output/political-event-weekly-v1
python scripts/build_weekly_artifact.py --period-start "${{ needs.validate-run-boundary.outputs.period_start }}" --as-of "${{ needs.validate-run-boundary.outputs.as_of }}" --generated-at "$(date -u +%Y-%m-%dT%H:%M:%S.000000Z)" --workflow-ref "${GITHUB_WORKFLOW_REF}" --source-run-id "${GITHUB_RUN_ID}" --producer-ref "${{ needs.validate-run-boundary.outputs.producer_ref }}" --source-events data/output/rss_source_pipeline/source_events.csv --watchlist "${WATCHLIST_PATH}" --feed-status data/output/rss_source_pipeline/source_fetch_status.json --output-dir data/output/political-event-weekly-v1 --run-mode "${RUN_MODE}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pin manual source inputs for official artifacts

When a manual dispatch overrides feeds_path, aliases_path, watchlist_path, or max_items_per_feed, the boundary guard still succeeds because it checks only run identity and period, and this command publishes the resulting CSV/watchlist as the official weekly artifact without recording those overrides. A test run using a reduced feed set or example watchlist can therefore upload political-event-weekly-v1 that looks like canonical weekly provenance while silently omitting or changing the source universe.

Useful? React with 👍 / 👎.

Co-Authored-By: Codex <noreply@openai.com>
@Pigbibi

Pigbibi commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

PERMANENT_FREEZE/CLOSE_RESLICE: terminal review failure after absolute-final closure. Preserve branch, commits, reviews, checks, and tests; no further code action.

@Pigbibi Pigbibi closed this Jul 15, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aee50fd8fb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +257 to +258
if lock_object.source_snapshot_digest != _snapshot_digest(files[EVENTS], files[WATCHLIST]):
raise _fail("source_snapshot_mismatch")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bind declared source artifacts to CSV bytes

When a bad producer or regenerated manifest changes source_artifacts in either JSON contract, this still accepts the artifact because only the combined snapshot digest is checked; the per-file hashes and row counts declared in political_event_weekly.json/period_lock.json are never compared with the included CSV bytes. That can bless an artifact whose advertised provenance points at different source files than the ones consumers actually receive.

Useful? React with 👍 / 👎.

Comment on lines +233 to +234
if not isinstance(files, Mapping) or tuple(files) != ARTIFACT_FILES or any(type(files[name]) is not bytes for name in ARTIFACT_FILES):
raise _fail("artifact_file_set_invalid")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Accept artifact file mappings in any order

When a consumer loads a valid downloaded artifact from disk, the insertion order of the mapping can differ from ARTIFACT_FILES (for example when iterating a directory), but the artifact still contains exactly the required five names. Comparing tuple(files) makes parse_weekly_artifact reject those valid artifacts solely because of mapping order, which is not preserved by the artifact contract.

Useful? React with 👍 / 👎.

Comment on lines +251 to +252
if lock_object.period_start != start or lock_object.as_of != as_of or lock_object.workflow_ref != WORKFLOW_REF or lock_object.producer_ref != contract.producer_ref:
raise _fail("period_lock_mismatch")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require source provenance to match across contracts

When either JSON contract is regenerated with a different valid source_provenance, the artifact still parses because this consistency check only compares period/workflow/producer fields. The manifest hard-codes the official provenance, so a single accepted artifact can contain contradictory provenance claims that downstream consumers may trust differently.

Useful? React with 👍 / 👎.

Comment on lines +153 to +154
if counts["feed_count"] <= 0 or counts["successful_feed_count"] != counts["feed_count"] or any(counts[key] for key in ("failed_feed_count", "stale_feed_count", "missing_feed_count")) or not complete:
raise _fail("feed_status_incomplete")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Derive feed completeness from per-feed entries

When the status JSON records a failed feed in feeds but the summary counts still say complete (for example after a writer bug or hand-edited status), _status accepts it because this check trusts only the top-level counters. Since the weekly producer relies on this validation to fail closed on failed feeds, inconsistent feed evidence can still produce an official weekly artifact.

Useful? React with 👍 / 👎.

Comment on lines +131 to 132
if type(self.source_attempt) is not int or not 1 <= self.source_attempt <= MAX_SAFE_JSON_INTEGER:
raise _invalid("source_attempt_invalid")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep reruns pinned to attempt 1

When a GitHub rerun produces run_attempt 2+, the workflow passes that value into the weekly lock and fetches live RSS again; accepting attempts above 1 here mints a distinct official lock instead of comparing against attempt 1's immutable source snapshot as documented in docs/weekly_period_lock_contract.md. If feeds have rolled over, a rerun can publish a different or empty artifact for the same weekly period.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant