Skip to content

feat: add weekly source contract foundation - #25

Closed
Pigbibi wants to merge 2 commits into
mainfrom
codex/pert-weekly-contract-pr0
Closed

feat: add weekly source contract foundation#25
Pigbibi wants to merge 2 commits into
mainfrom
codex/pert-weekly-contract-pr0

Conversation

@Pigbibi

@Pigbibi Pigbibi commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Scope

Pure producer-side political_event_weekly.v1 contract foundation only. No fetcher, workflow, artifact publication, QAR consumer, Pages, publisher, monthly, new data source, credential, or dependency changes.

Contract

  • Complete UTC ISO week: Monday 00:00Z through exclusive next Monday; as_of is the Sunday date.
  • generated_at is explicit actual UTC build time and must be at/after the exclusive period boundary.
  • schema_version, contract_version, cadence, run mode, producer SHA, provenance, source artifact digests/row counts, and complete feed status are strict and deterministic.
  • Any partial/failed/stale/missing feed, unsafe/duplicate input artifact, mismatch, unknown field, or malformed timestamp fails closed.
  • Scheduled/manual mode is explicit; no wall-clock/latest-source cutoff inference.

Source-of-truth decision: weekly period uses complete UTC ISO week and Sunday as_of.

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 allowed: blocking findings were cleared by independent Codex arbitration

⚖️ Codex Review Arbitration

clear: The cumulative diff demonstrably fixes both prior blockers. In src/political_event_tracking_research/weekly_contract.py, serialize_weekly_contract() now copies every feed_status field directly from contract.feed_status into the payload and immediately revalidates that payload via parse_weekly_contract(payload), so it no longer hardcodes completion fields or derives successful_feed_count from feed_count. The new parser/serializer contract is exercised by tests/test_weekly_contract.py::test_real_feed_counters_round_trip_without_serializer_defaults and the incomplete-status rejection cases. For artifact paths, _artifact() now rejects non-canonical aliases by requiring path == str(PurePosixPath(path)) and forbidding absolute paths, trailing slashes, //, ., .., and backslashes before duplicate detection runs on the parsed paths; tests/test_weekly_contract.py::test_artifacts_are_sorted_and_duplicate_or_unsafe_inputs_fail_closed specifically covers a//b.csv, a/./b.csv, ./a.csv, and duplicate inputs. No current finding remains valid, and there is no conflict with the prior required behavior.

ℹ️ Other Findings

1. 🟡 [MEDIUM] Security in src/political_event_tracking_research/weekly_contract.py

source_artifacts[].path is only checked for ASCII and path-shape constraints, so control characters such as \n, \r, \t, or NUL still pass validation. That allows contracts to carry paths that are ambiguous in logs/manifests and can be interpreted differently by downstream tooling, which violates the contract's path-safety requirement. (line 89)

Suggestion: Reject non-printable/control characters in artifact paths, for example by requiring every character to be in a safe printable subset (or matching an explicit allowlist regex such as ^[A-Za-z0-9._/-]+$).

2. 🟡 [MEDIUM] Logic in src/political_event_tracking_research/weekly_contract.py

source_provenance is accepted as any non-empty string. This means visually confusable Unicode variants, control characters, and newline-containing values all pass, even though provenance is part of the public contract identity and is described as strict/deterministic. Two logically identical provenance labels can therefore serialize to different bytes, and unsafe values can break downstream comparison, hashing, or logging. (line 123)

Suggestion: Validate source_provenance as a canonical identifier: reject control characters, normalize or require NFC, and preferably constrain it to a documented printable/ASCII allowlist or enum.


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: f0be26b8ec

ℹ️ 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".

"period_end_exclusive": contract.period_end_exclusive.isoformat(),
"generated_at": contract.generated_at.isoformat(timespec="microseconds").replace("+00:00", "Z"),
"run_mode": contract.run_mode, "producer_ref": contract.producer_ref, "source_provenance": contract.source_provenance,
"source_artifacts": [{"path": item.path, "sha256": item.sha256, "row_count": item.row_count} for item in contract.source_artifacts],

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 Serialize artifacts in canonical order

When producers build WeeklySourceContract directly with multiple source_artifacts, this preserves the tuple's input order even though parse_weekly_contract normalizes artifacts by path and the contract promises deterministic output. The same logical weekly contract can therefore produce different byte streams depending on artifact discovery order, which breaks downstream digest/signature comparisons; serialize the normalized/sorted artifact list instead of the raw tuple order.

Useful? React with 👍 / 👎.

"generated_at": contract.generated_at.isoformat(timespec="microseconds").replace("+00:00", "Z"),
"run_mode": contract.run_mode, "producer_ref": contract.producer_ref, "source_provenance": contract.source_provenance,
"source_artifacts": [{"path": item.path, "sha256": item.sha256, "row_count": item.row_count} for item in contract.source_artifacts],
"feed_status": {"feed_count": contract.feed_status.feed_count, "successful_feed_count": contract.feed_status.successful_feed_count, "failed_feed_count": 0, "stale_feed_count": 0, "missing_feed_count": 0, "complete": True},

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 Preserve feed failure counts during validation

When a producer constructs WeeklyFeedStatus directly with any nonzero failed/stale/missing count, this payload overwrites those fields with zero before parse_weekly_contract(payload) validates it. If successful_feed_count still equals feed_count, serialization emits a complete-looking contract instead of failing closed for the partial feed state; include the actual counts in the payload and let the parser reject invalid statuses.

Useful? React with 👍 / 👎.

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

@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: 315a389a03

ℹ️ 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 +95 to +99
if (
type(path) is not str or not path or not path.isascii() or unicodedata.normalize("NFC", path) != path
or canonical is None or canonical.is_absolute() or path != str(canonical) or path.endswith("/")
or "//" in path or any(part in {"", ".", ".."} for part in canonical.parts) or "\\" in path
):

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 Reject control characters in artifact paths

When an artifact path contains ASCII control characters such as \u0000 or \n, this guard still accepts it: path.isascii() passes, PurePosixPath preserves it as a relative part, and none of the later checks rejects it. A contract with source_artifacts[0].path = "data/live/a\u0000.csv" therefore parses and serializes even though any later digest/row-count step that opens base / artifact.path will fail with an embedded-null ValueError instead of failing closed for an unsafe artifact path.

Useful? React with 👍 / 👎.

@Pigbibi

Pigbibi commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

PERMANENT_FREEZE/CLOSE_RESLICE: actionable path-control-character finding remains after final closure. Preserve branch/worktree/commits/review threads/tests as evidence.

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