Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
aa5db87
ci: gate what a package's registry page will actually show
seonghobae Sep 17, 2026
271cd1b
ci: stop the description gate firing on a product's own domain
seonghobae Sep 18, 2026
fcccbf8
ci: let the directory name advise, not block
seonghobae Sep 18, 2026
22b16e6
fix(ci): remove template injection from the description gate
seonghobae Sep 18, 2026
e7f34ea
fix(ci): bind package gate to immutable release evidence
seonghobae Sep 19, 2026
ad526e3
merge: integrate protected main into package boundary gate
seonghobae Sep 19, 2026
42c4939
test(package): close credential and metadata fallbacks
seonghobae Sep 19, 2026
3b8ceba
fix(package): fail closed on built metadata boundary
seonghobae Sep 19, 2026
fe0a908
docs(package): record fail-closed artifact boundary
seonghobae Sep 19, 2026
9715362
test(package): reject ambiguous archive metadata
seonghobae Sep 19, 2026
a5603d8
fix(package): bind descriptions to standard metadata roots
seonghobae Sep 19, 2026
1be0bf0
docs(package): cite authoritative metadata roots
seonghobae Sep 19, 2026
513302a
test(strix): bind evidence helper to trusted source root
seonghobae Sep 19, 2026
c08b13d
fix(strix): resolve evidence binder from trusted source
seonghobae Sep 19, 2026
ca7e1e7
test(strix): materialize trusted binder fixtures
seonghobae Sep 19, 2026
db1fd61
fix(strix): restore complete verified fixture tree
seonghobae Sep 19, 2026
fb9c0e2
test(strix): require consumer-free trusted binder fixture
seonghobae Sep 19, 2026
78b33a8
repair(strix): restore executable harness after binary blob corruption
seonghobae Sep 19, 2026
191bd63
test(strix): require binder-free consumer fixture
seonghobae Sep 19, 2026
ef1a866
test(strix): separate trusted gate from consumer root
seonghobae Sep 19, 2026
abc9a7d
docs(strix): bind consumer isolation evidence to exact commits
seonghobae Sep 19, 2026
00082e8
docs(strix): describe separated trusted runtime fixture
seonghobae Sep 19, 2026
782d67b
test(strix): retain canonical gate source under scan
seonghobae Sep 20, 2026
a8d6261
test(strix): red specialized fixture owner boundary
seonghobae Sep 20, 2026
bbe225d
test(strix): materialize specialized fixtures' trusted runtime outsid…
Sep 21, 2026
1794626
test(strix): pin trusted evidence-binder path
seonghobae Sep 21, 2026
361a9eb
merge: adopt current CI owner and repair CodeQL URL oracle
seonghobae Sep 26, 2026
1fd22f4
test(strix): require Job Analysis authority context
seonghobae Sep 26, 2026
808a8a7
fix(strix): include Job Analysis authority context
seonghobae Sep 26, 2026
b90d873
docs(strix): record Job Analysis authority context
seonghobae Sep 26, 2026
2944ae0
merge(owner): restack registry description gate on current security s…
seonghobae Sep 26, 2026
0fa8ed6
merge(main): restack package description boundary onto current main
seonghobae Sep 28, 2026
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
144 changes: 144 additions & 0 deletions .github/workflows/package-description-boundary.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Reusable gate (workflow_call) for what a package's registry page will show.
#
# A repository README is developer-facing; links to internal design records and
# working notes belong there. The same file becomes the package description on
# PyPI, npm, or crates.io, where only the distribution's own files exist and the
# reader is installing rather than developing. A repo-relative link that works on
# GitHub is a 404 on the registry page, and internal go-to-market framing, a
# hard-coded deal value, or a quoted module path is plumbing on a product page.
#
# Motivated by a live finding: fast-mlsirm 0.11.3's published description carried
# 68 of these, including a KRW-denominated readiness gate and 16 dead links.
#
# This checks the BUILT description (PKG-INFO from an sdist, METADATA from a
# wheel) rather than the README on disk, because that is what a registry renders.
# Repositories with no release yet can point `readme` at their README instead.
#
# The `on:` trigger stays in each calling repo: a workflow_call target cannot
# also be triggered directly. Pin `uses:` to this file's commit SHA, never @main.
#
# name: Package description boundary
# on:
# pull_request:
# jobs:
# package-description-boundary:
# uses: ContextualWisdomLab/.github/.github/workflows/package-description-boundary.yml@<commit-sha>
# with:
# build: sdist
# dist-path: dist
#
# Converting an existing standalone check to this renames the published context
# to "<calling job> / package-description-boundary"; update branch protection in
# the same window (ADR 0024).
name: Package description boundary

on:
workflow_call:
inputs:
build:
description: "What to build before checking: sdist, wheel, or none to check the README instead"
type: string
default: "sdist"
dist-path:
description: "Directory holding the built sdist or wheel"
type: string
default: "dist"
readme-path:
description: "README to check when no distribution is built yet"
type: string
default: "README.md"
python-version:
description: "CPython used to build the distribution"
type: string
default: "3.12"
allow:
description: "Space-separated rule names to report without failing, for a staged migration"
type: string
default: ""

permissions:
contents: read

jobs:
package-description-boundary:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check out the calling repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Validate called workflow identity
env:
WORKFLOW_REPOSITORY: ${{ job.workflow_repository }}
WORKFLOW_SHA: ${{ job.workflow_sha }}
run: |
set -euo pipefail
if [ "$WORKFLOW_REPOSITORY" != "ContextualWisdomLab/.github" ]; then
echo "called workflow repository mismatch: $WORKFLOW_REPOSITORY" >&2
exit 2
fi
if [[ ! "$WORKFLOW_SHA" =~ ^[0-9a-f]{40}$ ]]; then
echo "called workflow SHA is not an exact commit: $WORKFLOW_SHA" >&2
exit 2
fi
- name: Check out the central gate
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
repository: ${{ job.workflow_repository }}
ref: ${{ job.workflow_sha }}
path: .central-gate
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97
with:
python-version: ${{ inputs.python-version }}
- name: Set up pinned uv build frontend
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
with:
version: "0.11.28"
enable-cache: false
# `build` is a bounded choice, never a command. An earlier revision took a
# free-form `build-command` and interpolated it into this `run:` block,
# which is template injection (a caller could pass arbitrary shell) and is
# also what ADR 0023 forbids: reusable inputs are data and capability
# flags, not shell source. Semgrep's run-shell-injection rule caught it
# before this shipped.
- name: Build the distribution
if: ${{ inputs.build != 'none' }}
env:
BUILD: ${{ inputs.build }}
DIST_PATH: ${{ inputs.dist-path }}
run: |
set -euo pipefail
case "$BUILD" in
sdist) target=--sdist ;;
wheel) target=--wheel ;;
*) echo "build must be one of: sdist, wheel, none (got: $BUILD)" >&2; exit 2 ;;
esac
uv build "$target" --out-dir "$DIST_PATH"
- name: Check the description a registry will render
env:
DIST_PATH: ${{ inputs.dist-path }}
README_PATH: ${{ inputs.readme-path }}
BUILD: ${{ inputs.build }}
ALLOW: ${{ inputs.allow }}
run: |
set -euo pipefail
allow_args=()
for rule in $ALLOW; do allow_args+=(--allow "$rule"); done
if [ "$BUILD" = "none" ]; then
source_args=(--readme "$README_PATH")
else
source_args=(--dist "$DIST_PATH")
fi
python .central-gate/scripts/ci/package_description_boundary.py \
"${source_args[@]}" \
--json package-description-boundary.json \
"${allow_args[@]}"
- name: Upload the boundary report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.0
with:
name: package-description-boundary
path: package-description-boundary.json
if-no-files-found: ignore
17 changes: 17 additions & 0 deletions CHANGELOG.d/20260920-strix-trusted-binder-runtime-fixture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
### Strix keeps trusted evidence binding outside consumer workspaces

- The Strix gate resolves its evidence binder beside the trusted gate source.
The executable core harness now materializes that trusted runtime under a
separate source directory, passes a binder-free consumer workspace through
`STRIX_REPO_ROOT`, and invokes the trusted gate by its absolute path.
- OpenCode coverage assertions follow the consolidated
`validate-pr-metadata` owner instead of the removed
`coverage-source-tree` job and failure-report step.
- The commercial-readiness receipt contract now compares the complete parsed
harden-runner endpoint set instead of treating an expected hostname as a URL
substring. This closes the exact CodeQL
`py/incomplete-url-substring-sanitization` finding without suppressing it or
widening egress.
- The branch adopts the current central dependency owner, including the
explicit AnyIO 4.14.2 source-to-hash pin required by the Python security
gate.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@

- The queue-health workflow contract now pins both workflow-level and collector-job permissions to exactly `contents: read` plus `actions: read`, rejecting scalar `read-all`/`write-all`, quoting/spacing variants, inline maps, and unexpected write scopes.

### Strix supplies bounded Job Analysis authority context from the trusted base

- Orgmetra #63 changes `packages/hris-kernel/src/orgmetra_hris_kernel/job_analysis.py`, but the Strix scan workspace previously omitted the unchanged authorization, HTTP, snapshot, and persistence collaborators that establish its resource-ownership boundary. That incomplete context produced a false HIGH IDOR finding even though the product reconstructs owner scope and authorizes resource fields before port access. A source-first executable fixture now requires the changed PR-head module, exactly five unchanged Job Analysis authority files from the authenticated trusted base, and exclusion of an unrelated administration file. RED `1fd22f4e` failed because `auth.py` was absent; the gate now recognizes only the normalized Job Analysis trigger and adds the five fixed context paths through the existing trusted-base materialization boundary. No consumer source, provider/model policy, severity gate, timeout, or write authority changes.

### OpenCode coverage image materializes every Dockerfile lock input

- Required OpenCode run `35370902053` for `.github#2266@12621f75e` failed before executing PR code because its trusted Dockerfile copied `requirements-noema-document-ci-hashes.txt` while the isolated build context contained only the OpenCode lockfile. The coverage owner now validates both lockfiles as regular non-symlink files and copies both into the trusted build context before the networked image build. `tests/test_opencode_agent_contract.py` pins the complete input boundary. Hosted exact-head acceptance remains Proposed until the new run reaches the image-build and coverage steps.
Expand Down
163 changes: 163 additions & 0 deletions docs/doctoring/package-description-boundary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# The package description is not the repository README

Date: 2026-09-18

## The finding

`fast-mlsirm` 0.11.3's published PyPI description carried a `## Commercial
Readiness` section: an enterprise sales gate, a KRW 2,000,000,000 product gate,
buyer packet and procurement due-diligence links, and a transcript of the
internal evidence pipeline. Checking the live artifact rather than the README on
disk found 68 boundary findings in `fast_mlsirm-0.11.3.tar.gz`, including 16
repo-relative links that are 404s on the registry page because only `LICENSE`
and the package sources ship in the distribution.

Two other live packages were checked the same way: `threadweave` 0.1.0 and
`rankweave` 0.1.0 each carry 3 dead links, one of them pointing at a source
module path. `appguardrail` and `egressweave` carry dead links but no
vocabulary leak.

## Why a README check is not enough

The registry renders the description recorded in the built artifact — PKG-INFO
for an sdist, METADATA for a wheel — not the file in the repository. Those can
differ: a release is built from a commit, and packaging config decides what is
included. `scripts/ci/package_description_boundary.py` therefore reads the
built artifact and falls back to the README only for a repository that has not
released yet.

## What is and is not a finding

A repository README may link internal design records; that is public
development history and the organization publishes it deliberately. The same
text on a registry page is different, because only the distribution's files
exist there and the reader is installing rather than developing.

Blocking mechanical defects:

- **relative-link** — works on GitHub, 404 on the registry page.
- **mutable-release-link** — a published contract points at GitHub's moving
`main`, `master`, or `develop` branch instead of a release tag or exact
commit.
- **source-path** — a quoted module path tells the reader which file implements
a feature instead of what they can do with it.
- **monetary-target** — a hard-coded internal deal value. The organization
already decided this in `fast-mlsirm`'s
`docs/doctoring/acquisition_readiness_gate.md`: commercial readiness
verification may validate an explicitly supplied deal scenario, but product
quality evidence must not depend on a hard-coded monetary target. Its CLI
defaults `--contract-value-krw` to unset for that reason; a public package
page must follow the same rule.

Advisory unless a repository opts into `--strict`:

- **internal-working-record** — `docs/superpowers`, `docs/product`,
`docs/commercial`, `docs/planning`, `docs/doctoring`. These often hold
internal evidence, but some repositories intentionally keep public operator
guidance there.
- **go-to-market-vocabulary** and **requirement-map** — sales framing and
PRD/TRD implementation tables usually are internal artifacts, but can be
legitimate product-domain vocabulary.

Deliberately not a finding:

- **`docs/adr` links.** A public architecture decision record is legitimate to
advertise. It only has to be an absolute URL.
- **Product vocabulary that happens to look commercial.** `appguardrail` ships
a real `buyer-diligence` CLI subcommand and `scopeweave` really does check
procurement packages. Those belong in their documentation. The rule targets
internal framing, not a product's domain.

## Why two of the rules only advise

The first run against the three repositories that had just been corrected found
16, 1 and 2 remaining "findings" — and nearly all of them were wrong.

`contextual-orchestrator` genuinely ships `/api/v1/commercial_readiness/latest`,
`/api/v1/saleability_decisions/latest` and `/api/v1/commercial_due_diligence_rooms/latest`,
with tests named after them. `wardnet`'s crate genuinely computes commercial
readiness snapshots. `semantic-data-portal`'s single finding was the sentence
that explains PRD/TRD records are excluded. The stated exception — product
domain vocabulary is not a leak — was in the prose but not in the regex.

A gate that fires on a correct fix gets switched off the first week. So
`go-to-market-vocabulary` and `requirement-map` now report without failing, and
`--strict` promotes them for a repository that wants them enforced. The
mechanical rules still block, because they need no judgement: a relative link is
dead on the registry page, a quoted module path is plumbing, and a hard-coded
deal value is never a product feature.

The same pass found ADRs filed under `docs/planning/adrs/` being flagged by the
`docs/planning/` prefix. An architecture decision record is a public design
record wherever a repository files it, so the path check now exempts it.

Checked after the change: the live `fast_mlsirm-0.11.3.tar.gz` still reports 22
blocking findings (63 with `--strict`), the corrected fast-mlsirm README is
clean, and the three corrected READMEs pass with advisory notes only.

## The directory name cannot decide, either

A second false positive came from real data. `pg-llm-batch`'s README links
`docs/doctoring/bootstrap-dsn-precedence.md`, `cli-secret-input.md` and
`postgres-logical-restore.md` — and those are operator documentation a package
user genuinely needs, not internal working notes. That repository files
operational guidance under the same directory name this one uses for incident
records. A worker removed those links to satisfy the gate, then restored them in
the next commit because they were legitimate, which is the gate causing damage
rather than preventing it.

So `internal-working-record` advises too. Nothing is lost: a link into such a
directory that is ALSO repo-relative is still blocked, by `relative-link`, and
that is the mechanical defect — the page cannot resolve it. An absolute URL to
the same file resolves fine, and whether that audience wants it is judgement.

The pattern across both corrections: block only what is broken regardless of
context, advise on anything that needs to know what the product is.

## The gate's own first revision failed the org's SAST gate

Worth recording, because it is the same class of mistake this document warns
about. The first revision of the reusable workflow took a free-form
`build-command` string input and interpolated it directly into a `run:` block.
That is template injection — a caller could pass arbitrary shell into a
workflow running in its own repository context — and it is exactly what ADR
0023 forbids: reusable inputs are data and capability flags, not shell source.

The contract test for the fast-mlsirm reusable workflow asserts that no input
reaches a `run:` body. The same rule was not applied here, and the author did
not notice until Semgrep's `run-shell-injection` rule failed the PR.

The input is now `build: sdist | wheel | none`, validated in-shell so an
unexpected value exits loudly, and a contract test pins both the absence of
`build-command` and the absence of any `${{ inputs.* }}` inside a `run:` block.

## Staged adoption

`--allow <rule>` reports a rule without failing, so a repository mid-migration
can adopt the gate before its README is fully converted rather than landing a
red check it cannot fix in one PR.

## Reusable-workflow and release-artifact identity

The called workflow cannot use `github.workflow_sha` to retrieve its own source:
inside a reusable workflow the `github` context remains associated with the
caller. The gate therefore validates `job.workflow_repository` as
`ContextualWisdomLab/.github`, validates `job.workflow_sha` as a 40-hex commit,
and uses those two called-job fields for the central checkout. There is no
caller-SHA fallback.

The build frontend is the exact-action-pinned `astral-sh/setup-uv` with an exact
uv version; the inherited workflow performs no unhashed runtime `pip install`.
The caller checkout sets `persist-credentials: false` before any project build
backend runs. When `build` requests an sdist or wheel, a missing or empty dist
path is an error from the central gate; only the explicit `build: none` mode may
inspect a README, so a failed artifact boundary cannot silently downgrade itself.
PyPA's source-distribution and wheel specifications identify the root
`{name}-{version}/PKG-INFO` and `{distribution}-{version}.dist-info/METADATA`
as the authoritative metadata locations. The gate requires exactly one such
root entry and rejects ambiguous archives instead of selecting by path depth or
ZIP order. When a dist directory contains both an sdist and wheel, all candidate
metadata descriptions must be identical or the gate fails closed. Published Markdown
links using GitHub's `blob/main`, `tree/main`, `master`, or `develop` forms are
blocking `mutable-release-link` findings; release tags and exact commits remain
valid.
Loading
Loading