Repository navigation
Enforce T3 sign-off records for --allow-t3 (default on, private record path, no opt-out) - #123
Open
CompleteDotTech wants to merge 124 commits into
Open
CompleteDotTech wants to merge 124 commits into
CompleteDotTech wants to merge 124 commits into
Conversation
Document the global --allow-t3 flag (before the command) in SKILL.md (synopsis, Command Reference, co-owner sign-off paragraph and the refusal sentence), the Unreleased CHANGELOG, and the org and projects references. The flag applies to every repository command, generator, doctor, lint and the org and project groups. It lifts only the T3 refusal for one invocation; excluded and HOLD owners stay refused. Add tests/test_allow_t3_docs.py to check the synopsis, the refusal text, the CHANGELOG entry and the --help output. No behaviour change; scripts, the shared package validator and its contract test are untouched. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Document the global --allow-t3 flag (before the command) in SKILL.md: the command synopsis, the refusal sentence, and a co-owner sign-off paragraph. The flag applies to every repository command, generator, doctor, lint and the org and project groups, and meta, org and project also accept it after the command name. It lifts only the T3 refusal for one invocation; excluded and HOLD owners stay refused, and it is never read from a config file or environment. Add the flag to the Unreleased Added list in CHANGELOG.md and note that the global flag is equivalent in references/org.md and references/projects.md. Add tests/test_allow_t3_docs.py to check the synopsis, the refusal text, the CHANGELOG entry and the --help output. No behaviour change; scripts, the shared package validator and its contract test are untouched. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Move the Unreleased changelog entry from Fixed into the Added list. Note that HOLD is refused only for repository commands and generators. The org about, pins, health and doctor actions pass allow_hold, so they admit a HOLD org with or without the flag. Make test_help_text_matches_docs cross-check the --help usage line against the SKILL.md synopsis, so it fails on a base without the doc change. Also check the changelog entry sits in the Unreleased Added list. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> # Conflicts: # SKILL.md # tests/test_allow_t3_docs.py
…ses T3 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…exceptions doctor forces R7=hold only without the flag; local meta and fingerprint do not check owner eligibility. Pinned by a docs test. No behaviour change. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…test Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Replace the process-wide _T3_SIGNED_OFF module global with a ContextVar so --allow-t3 sign-off cannot leak between concurrent or embedded calls. set_t3_signed_off now returns a token, reset_t3_signed_off restores the prior value, and showcase.main() wraps each invocation in that token. Default stays unsigned; CLI behaviour is unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
With the flag (or SHOWCASE_REQUIRE_T3_SIGNOFF=1), --allow-t3 is honoured only when decisions/t3-signoffs.yaml in the estate bundle holds an approved, unexpired record for the owner (and repository when scoped); otherwise exit 2 with t3-signoff:missing|expired|scope. Writer jobs carry signoff_id/approval_id and doctor shows signoff_id. Default behaviour unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…d ids, honour aliases, show sign-off in doctor (#23) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ied scopes, strict expiry, malformed scopes, repo threading, report stamping (#23) Fresh VM verification: not run. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… malformed sign-off files, test HOLD owners (#23) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
tests/test_allow_t3_global.py moves from CRLF (the only CRLF Python file at base) to the repo's LF convention; review with --ignore-cr-at-eol. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Integrate the T3 sign-off record check with #28 and #31: - signoff.py state (required flag, grants, aliases) is context-local (ContextVar), matching #28's --allow-t3, so one thread's opt-in or grants cannot leak into another's owner gate. showcase.main() opens a fresh run state and restores the enclosing one on exit. Tests: test_signoff_state_is_context_local (fails with a module-global required flag) and test_main_restores_an_enclosing_runs_signoff_state. - config.t3_signed_off(owner, repo) reads the ContextVar and defers to signoff.granted under the opt-in flag. - SKILL.md synopsis lists [--require-t3-signoff] and the #31 sign-off paragraph names its refusal codes; test_allow_t3_docs follows --help. - content.py and the new test file keep LF endings (issue/23 had converted content.py to CRLF, a whole-file diff on a shared file). - The default record path decisions/t3-signoffs.yaml is marked in signoff.py as the working default pending the maintainer's choice. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…-write grants, stamped change records (#23, #24, #26) - --t3-signoff-file PATH / SHOWCASE_T3_SIGNOFF_FILE: no default location (the canonical bundle path is the maintainer's choice); strict mode without a file refuses t3-signoff:no-file. - references/t3-signoff.schema.json + references/t3-signoff.md: id, owner, repos, approved, approver, approved_on, expires, note. Records breaking it refuse t3-signoff:invalid with the first problem instead of a silent t3-signoff:missing. Synthetic fixtures under tests/fixtures. - Timestamp expiries compare as aware instants; naive timestamps are invalid; date expiries stay inclusive through that day. - Grants and aliases are copy-on-write ContextVar values, so inherited asyncio contexts no longer leak a child's grant into the parent. - Change records get signoff_id (no approval_id), in --records files and JSON output; star-List jobs that org pins plans on the personal account carry the T3 org's sign-off explicitly. - Docs: SKILL.md, CHANGELOG, constraints.md, ONBOARDING. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…m jobs, refuse mixed-type keys (#23, #26) - signoff._parse_when: a string must match the schema's date or awareTimestamp pattern before parsing, so compact (20991231), week (2099-W52-1) and +00 offset forms are invalid at runtime as they are in the schema. A test applies the schema's patterns with re, so it runs without jsonschema. - signoff.problems: sort unknown keys with a type-aware key, so a record with `unexpected: true` and `7: unexpected` refuses t3-signoff:invalid instead of raising TypeError. - org star-List item jobs that add another owner's T3 repository now carry that repository's record as signoff_id/approval_id (signoff.stamp_source), with a T1-list/T3-favorite regression test. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…offs, enforce the record-file envelope, restore LF (#23, #26) - org labels: a member whose approved pending transfer resolves to a T3 org passes check_owner for that org (held otherwise); its label jobs carry the record. - _t3_held checks the favorite's current slug as well as the named one. - topics --also-in: the record that admitted a T3 membership topic is carried onto the change record (signoff_id; signoff_ids when several apply). - signoff.load_records rejects files whose envelope breaks the schema (top-level list, extra top-level key, non-object entry): t3-signoff:missing. - CHANGELOG.md and references/t3-signoff.md back to LF; .gitattributes pins them. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…dopt and estate-plan associations, restore CRLF (#23, #26) - org: the transfer-target T3 checks (labels_plan gate, _t3_held current slug) now run only under --require-t3-signoff; the flag-off default matches base 2be04ac (new flag-off test). The CHANGELOG "Fixed" entry moves into the opt-in flag's entry. - org.public_slice: under the flag, a home repository a T3 org holds today (approved pending transfer) is omitted until that org's record covers it. - signoff.stamp_associations: shared association stamping (cmd_topics, intake.adopt, project.estate_plan); keeps an existing approval_id. - adopt: topics job and change record carry topics.merge signoff_ids. - project.estate_plan: T3 orgs keep their record id; jobs naming them carry it. - tests/test_allow_t3_global.py: base CRLF restored, diff vs base is +108/-0. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…path Rewrite references/t3-signoff.md to the agreed interface: --require-t3-signoff is on by default, the canonical record file is <estate_root>/decisions/ t3-signoffs.yaml (resolver.estate_root(); SHOWCASE_ESTATE_ROOT or the skill's parent bundle), and --t3-signoff-file / SHOWCASE_T3_SIGNOFF_FILE override it. Removes the resolved open-decision section. Format, YAML example and synthetic-fixture note are unchanged. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…rd path (#23, #24, #25, #26, #27) --allow-t3 now needs a valid, unexpired record by default. The record file resolves as --t3-signoff-file, then SHOWCASE_T3_SIGNOFF_FILE, then <estate root>/decisions/t3-signoffs.yaml; a missing file or unavailable estate root refuses t3-signoff:no-file naming the path and the tool never creates the file. --no-require-t3-signoff (or SHOWCASE_REQUIRE_T3_SIGNOFF=0) is the explicit, logged opt-out. OWNER/NAME repositories are covered only by the record's own org, and estate-plan stamping ignores display names shared with another org. Existing trust-only tests use a synthetic record fixture; docs and CHANGELOG updated. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…repo-canonicalization-50f) regressions and docs Owner integration of Haiku results ef2d8ac (signoff.canonical_repo, evaluate/_covers canonicalization) and 796bd75 (project copy-plan --repos validation, README entry normalisation, no-flag T3 README omission). - canonical_repo rejects `.`/`..` owners (`../x` fails closed). - Tests: evaluate spellings (x/, ' x', 'x ', x.git, x/.git) beside a `*` grant plus withdrawal; invalid names fail closed; CLI spellings (slash, .git, comma-space, bare, padded owner) omit withdrawn-x from stdout and --jobs; malformed --repos usage refusal; no-flag cross-owner omission (test_control_without_allow_t3_path_unchanged inverted). - Docs: references/t3-signoff.md spelling rules and issue #24 field mapping; CHANGELOG entry. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The pins order job tells the writer to make the ticked set exactly the kept list and untick every other repository. That would also withdraw a pin held for co-owner sign-off. Track targets with a T3 sign-off hold and skip the exact-set order job for them, logging pins-order-held:<target>:t3-signoff. The individual authorized pin jobs are unchanged. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…bad entries Stored grant and withdrawal scopes are now canonicalized the same way as the requested repository (_canon_entries, `*` preserved), in _covers, _scope_key and _withdrawn. `site/`, ` site` and `site.git` name `site`. An entry that cannot be canonicalized makes the record malformed (problems() reports `repos:`), so a withdrawal with such a scope is never silently ignored. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…es and held pin order Owner integration of round 13: _finish and showcase main stamp writer-shaped proposals (kept out of the writer queue); tests for canonicalized stored scopes, invalid booleans, held exact-set pin order and proposal stamping; docs and CHANGELOG. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Remove the f prefix from a placeholder-free string literal on line 1314 (ruff F541). No behaviour change. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…them Holds a view-filter mutation that names a repository without a valid T3 sign-off and attaches the covering sign-off ids to permitted filter jobs. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…rs; tests and docs Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
copy_plan rediscovered captured Org-option provenance from the rendered README text, which loses the grant when a former login has a different captured label. The README job now receives the signoff_id of every retained_t3 entry through signoff.stamp_associations, merged with ids already on the job. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
public_slice excludes .github, so its repository check never saw the profile's own destination. With --allow-t3 on a T3 owner, a refused sign-off for <owner>/.github now raises Refused (log t3-signoff:<code>:<login>/.github) before the README, data.json or assets are built or written. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ance regressions Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ptured Org options Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ly; carry labels to Org-option stamping captured_t3 tries the description remainder (T3 prefix only) and then the option name, so an unresolved description no longer hides a recognized T3 name. Retained orgs carry their captured labels; an org retained from two options merges them. _stamp_estate_orgs credits a job that quotes a label as a whole token, so the Org-options mutation carries the retained org's signoff_id. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…ame independently) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…esolution and Org-options stamping; docs Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> # Conflicts: # CHANGELOG.md
… view filters _filter_repo_refs tokenized the raw filter, so a positive repo: qualifier wrapped in parentheses (e.g. '(repo:A/x) workstream:Research') was not seen and its repository skipped sign-off enforcement and provenance stamping. Grouping parentheses outside quoted values are now replaced with spaces before tokenizing; quoted values are left unchanged. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
… permitted records Adds regressions for repo qualifiers wrapped in grouping parentheses: parser cases for _filter_repo_refs, withdrawn holds with parity against the ungrouped filter, missing-record holds, a permitted repo carrying the sign-off id, and a T1-only control. The new grouping cases fail against the current parser (the P1 bypass) until the production fix lands. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
… parentheses Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves #23
Resolves #24
Resolves #25
Resolves #26
Resolves #27
Change and affected skills
--allow-t3is no longer a trust-only claim. For T3 actions the skill reads a sign-off record and enforces it by default, with no opt-out flag or environment variable (passing--require-t3-signoffis an argparse error, as onmain).check_ownerreads and verifies a valid, unexpired, scope-matching record, rejects future and inverted dates and reports malformed covering records as invalid;approvedvalues that are not booleans next to a valid grant fail closed ast3-signoff:invalid; a later literalapproved: falsewithdraws a grant (t3-signoff:revoked); owner and repository spellings are canonicalized (case, whitespace, trailing slash,.git, former logins), and an invalid remaining name fails closed.signoff_id(signoff_idswhen several);approval_idis never set from a sign-off. Both source and destination grants are kept for star-List items and transfers, and the board-owner and repository-owner ids both appear on README jobs.--allow-t3flag), granted and regranted ones carry their grant ids, and standalone read-onlyproject readmeis unchanged. A populated capture with denied entries never falls back to a genericmain.(repo:Pick-Your-AI/withdrawn-x) workstream:Research) is now authorized like a bare qualifier: grouping punctuation is parsed outside quoted values (including nested groups), missing or withdrawn grants hold the view mutation, granted and regranted ones carry their sign-off ids, quoted literals and negative qualifiers are unaffected and the T1-only case is unchanged. Earlier in this PR: the.githubprofile destination is authorized before the profile README anddata.jsonare written; granted former-label (renamed) Org options keep their sign-off id; a captured Org option whose name resolves to a known T3 owner is recognized independently of its description; stored withdrawal scopes in non-canonical spellings are enforced; held pin order is suppressed while authorized pins are kept; content-action proposals carry their sign-off id without being promoted to queued jobs.approval_id"; jobs carrysignoff_idinstead, and a clarification comment was posted on E1.1.3 Carry the sign-off id into writer jobs and change records #26. The acceptance criteria are unchanged.--allow-t3#31 are related but not closed by this PR.Prior work
Searched open and closed PRs and issues again at this head (t3 signoff, sign-off record, allow-t3, view filter consent): this builds on merged #20 under epic #22; #124, #126 to #129, #131 and #133 are merged and unrelated; no other open PR covers the record enforcement (open #13 and #133 are unrelated).
Evidence (head
1d5f931c6e4d30b9b4cb624edef55e339c9e79e8, base main2243010d67725c17a163223d84f91a119f7262dd)vm-1d5f931c...-result.json).consent-complete118-1d5f931c...json): all 118 assertions pass (the earlier 108 plus 10 grouped-filter checks), using actualproject copy-planandproject estate-planCLI invocations (stdout and--jobs), the estate wrapper and direct-library cases with synthetic data and no GitHub writes. Earlier heads each failed a subset (for example 331b517 fails 8 grouped-filter checks); those receipts and reviews are history.Status: published for review. Independent reviews of this exact head (two Opus and Sol), hosted CI and the merge gate are outstanding; do not merge on the strength of this description. Shared validators (
scripts/validate_package.py,tests/test_package_contract.py) are byte-identical tomain.Boundaries
Activation keys, approval gates, specialist entry points and the command contract are unchanged. Consent is stricter, not looser. No registry release, visibility change or live external action is implied.
Family consistency
Single-repository change; no sibling skill PRs are affected.
🤖 Generated with Claude Code