Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
4dffd09
test(operations): require visible hourly scheduler activation evidence
seonghobae Aug 12, 2026
0de6df9
fix(operations): retain hourly scheduler activation evidence
seonghobae Aug 12, 2026
17687ff
test(operations): preserve fail-closed scheduler activation lane
seonghobae Aug 12, 2026
635e75c
docs(operations): explain scheduler activation RCA and feasibility
seonghobae Aug 12, 2026
e3ca6f0
docs(doctoring): record hourly scheduler activation RCA
seonghobae Aug 12, 2026
cf8ccee
docs(changelog): record scheduler activation evidence
seonghobae Aug 12, 2026
8d8a348
Merge protected main into hourly scheduler activation evidence
seonghobae Aug 12, 2026
7bfecff
test(security): keep public activation evidence configuration-opaque
seonghobae Aug 12, 2026
3eef1a4
fix(security): make scheduler evidence configuration-opaque
seonghobae Aug 12, 2026
b2f9b01
docs(operations): keep public scheduler evidence configuration-opaque
seonghobae Aug 12, 2026
7de959f
docs(doctoring): record public artifact confidentiality correction
seonghobae Aug 12, 2026
de68cdc
docs(changelog): keep activation evidence configuration-opaque
seonghobae Aug 12, 2026
dec5b00
Merge protected main into hourly scheduler activation evidence
seonghobae Aug 12, 2026
f653484
merge: refresh hourly activation evidence onto OpenAPI-integrated main
seonghobae Aug 12, 2026
e0fd69d
merge: refresh hourly activation evidence onto latest main
seonghobae Aug 12, 2026
4422dcd
chore(operations): merge latest protected main into scheduler activat…
seonghobae Aug 12, 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
103 changes: 99 additions & 4 deletions .github/workflows/hourly-commercial-readiness.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,110 @@ concurrency:
group: noema-hourly-commercial-readiness
cancel-in-progress: true

# The workflow token only checks out trusted default-branch code. All PR writes
# use the dedicated maintainer App token so merge events can trigger downstream
# push workflows instead of being suppressed by GITHUB_TOKEN recursion rules.
# The workflow token only reads trusted default-branch state. All PR writes use
# the dedicated Maintainer App token so merge events can trigger downstream push
# workflows instead of being suppressed by GITHUB_TOKEN recursion rules.
permissions:
contents: read

jobs:
activation_preflight:
name: scheduler-activation-preflight
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
write_ready: ${{ steps.activation.outputs.write_ready }}
terminal_classification: ${{ steps.activation.outputs.terminal_classification }}
env:
MAINTENANCE_ENABLED: ${{ vars.NOEMA_MAINTENANCE_ENABLED == 'true' }}
MAINTAINER_APP_CLIENT_ID_CONFIGURED: ${{ vars.NOEMA_MAINTAINER_APP_CLIENT_ID != '' }}
MAINTAINER_APP_PRIVATE_KEY_CONFIGURED: ${{ secrets.NOEMA_MAINTAINER_APP_PRIVATE_KEY != '' }}
REVIEWER_LOGIN_CONFIGURED: ${{ vars.NOEMA_REVIEWER_LOGIN != '' }}
REPOSITORY_FULL_NAME: ${{ github.repository }}
WORKFLOW_SOURCE_SHA: ${{ github.sha }}
EVENT_NAME: ${{ github.event_name }}
WORKFLOW_RUN_ID: ${{ github.run_id }}
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
steps:
- name: classify activation feasibility without repository writes
id: activation
shell: bash
run: |
set -euo pipefail
write_ready=false
terminal_classification=EXTERNAL_GATE_REMAINS
reason_code=activation_prerequisite_unavailable

if [ "$REPOSITORY_FULL_NAME" != "ContextualWisdomLab/noema" ]; then
terminal_classification=SAFETY_OR_POLICY_BLOCKER
reason_code=unexpected_repository
elif ! [[ "$WORKFLOW_SOURCE_SHA" =~ ^[0-9a-f]{40}$ ]]; then
terminal_classification=SAFETY_OR_POLICY_BLOCKER
reason_code=invalid_workflow_source_sha
elif [ "$MAINTENANCE_ENABLED" != "true" ] \
|| [ "$MAINTAINER_APP_CLIENT_ID_CONFIGURED" != "true" ] \
|| [ "$MAINTAINER_APP_PRIVATE_KEY_CONFIGURED" != "true" ] \
|| [ "$REVIEWER_LOGIN_CONFIGURED" != "true" ]; then
terminal_classification=EXTERNAL_GATE_REMAINS
reason_code=activation_prerequisite_unavailable
else
write_ready=true
terminal_classification=NO_ACTION_NEEDED
reason_code=write_lane_ready
fi

generated_at="$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
evidence_path="artifacts/operations/hourly-scheduler-activation.json"
mkdir -p "$(dirname "$evidence_path")"
jq -n \
--argjson schema_version 1 \
--arg repository_full_name "$REPOSITORY_FULL_NAME" \
--arg workflow_source_sha "$WORKFLOW_SOURCE_SHA" \
--arg event_name "$EVENT_NAME" \
--arg workflow_run_id "$WORKFLOW_RUN_ID" \
--arg workflow_run_attempt "$WORKFLOW_RUN_ATTEMPT" \
--arg generated_at "$generated_at" \
--arg terminal_classification "$terminal_classification" \
--arg reason_code "$reason_code" \
--argjson write_ready "$write_ready" \
'{
schema_version: $schema_version,
repository_full_name: $repository_full_name,
workflow_source_sha: $workflow_source_sha,
event_name: $event_name,
workflow_run_id: $workflow_run_id,
workflow_run_attempt: $workflow_run_attempt,
generated_at: $generated_at,
terminal_classification: $terminal_classification,
reason_code: $reason_code,
write_ready: $write_ready
}' >"$evidence_path"
chmod 0600 "$evidence_path"

{
echo "write_ready=$write_ready"
echo "terminal_classification=$terminal_classification"
} >>"$GITHUB_OUTPUT"
{
echo "## Hourly scheduler activation"
echo
echo "- Classification: \`$terminal_classification\`"
echo "- Reason: \`$reason_code\`"
echo "- Credential-bearing write lane: \`$write_ready\`"
} >>"$GITHUB_STEP_SUMMARY"

- name: upload scheduler activation evidence
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: scheduler-activation-evidence
path: artifacts/operations/hourly-scheduler-activation.json
if-no-files-found: error
retention-days: 90

maintain:
if: vars.NOEMA_MAINTENANCE_ENABLED == 'true'
needs: activation_preflight
if: needs.activation_preflight.outputs.write_ready == 'true'
name: commercial-readiness-maintenance
runs-on: ubuntu-latest
timeout-minutes: 45
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Changelog

## Unreleased
- 매시간 commercial-readiness scheduler에 항상 실행되는 read-only activation preflight를 추가해 whole-run skipped 상태를 없앤다. Preflight는 activation prerequisite를 내부에서만 평가하고, public-repository artifact에는 exact workflow source SHA·run identity·`write_ready`·configuration-opaque terminal classification/reason만 90일 보존한다. Credential-bearing Maintainer App write lane은 모든 activation prerequisite가 확인될 때만 열리며, 개별 variable/secret 존재 여부·기본 `GITHUB_TOKEN` 권한·reviewer key contract·branch protection·merge/release authority는 노출하거나 변경하지 않는다.
- 개발 의존성 체인의 transitive `nanoid` lockfile resolution을 `3.3.16`에서 `3.3.17`로 최소 갱신하여 GHSA-2v37-7h3g-55p8 / CVE-2026-67213 보안 게이트를 복구한다. PostCSS의 선언 범위 `^3.3.16`과 다른 package metadata는 변경하지 않으며 audit waiver·ignore·severity 완화 없이 `npm ci`/`npm audit --audit-level=high`가 exact head에서 재검증되도록 유지한다.
- `hourly-product-development`가 `NVIDIA_NIM_API_KEY`뿐 아니라 `NOEMA_MAINTAINER_APP_CLIENT_ID`와 `NOEMA_MAINTAINER_APP_PRIVATE_KEY` 존재를 checkout·OpenCode 설치·NVIDIA 호출 전에 검증한다. 게시 경로가 준비되지 않았으면 `maintainer_app_unavailable`로 실패 폐쇄하여 알려진 실패에 추론 비용을 쓰지 않으며, `dry_run`은 credential 없이 queue와 task contract를 검토하는 경로로 유지한다. 기존 reviewer App 및 `NOEMA_LLM_API_KEY`·`contextual-orchestrator` reviewer credential 경계는 변경하지 않는다.
- zero open pull requests일 때만 `NVIDIA_NIM_API_KEY` 전용 OpenCode 1.17.13 세션을 실행하는 proposal-only `hourly-product-development` 루프를 추가. minute-47 schedule·non-cancelling single flight·OpenCode binary SHA-256 pin·NVIDIA NIM model fallback·후보 실패 시 clean reset·GitHub/OIDC credential 제거·reviewer key 비참조·full release verification·40-file/500,000-byte proposal budget·trusted one-PR packaging을 강제한다. 각 후보 실행은 900초와 30초 kill grace로 제한하고, 실패 후 `npm ci --ignore-scripts` 재설치는 별도 60초와 10초 kill grace로 제한한다. 재설치가 실패하거나 시간 초과되면 불완전한 dependency tree로 다음 후보를 실행하지 않고 실패 폐쇄한다. 세 후보의 실행·종료 2,790초, 두 번의 후보 간 재설치 140초, 300초 setup/diagnostic reserve를 합친 3,230초가 55분(3,300초) job budget에 들어가며 70초 여유를 남긴다. 마지막 후보가 실패하면 불필요한 reset·clean·재설치를 생략하고 안정적인 전체 후보 실패 진단으로 곧바로 종료한다. 모델 실행, 제안 코드 검증, publication credential을 각각 별도의 GitHub-hosted runner로 분리하고, immutable artifact의 exact ID·workflow-run ID·archive digest와 patch SHA-256·base SHA·file/byte count를 교차 검증하며 symlink(`120000`)와 gitlink(`160000`)를 세 경계 모두에서 차단한다. 제안 코드를 실행한 runner에는 Maintainer App secret/token을 절대 제공하지 않고, 세 번째 non-executing publisher에서만 late-bound repository-scoped App token을 발급한다. merge/release/deploy authority는 기존 `hourly-commercial-readiness` exact-head governance에 유지하며, 운영 Runbook과 OpenCode/NVIDIA/GitHub Actions/NIST SP 800-218 근거를 APA 7th doctoring에 기록했다. package version은 release·deployment·production KPI evidence를 발행하지 않으므로 유지한다.
Expand Down
147 changes: 147 additions & 0 deletions docs/doctoring/hourly-scheduler-activation-feasibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# Hourly Scheduler Activation RCA and Feasibility

## Status

- Decision state: Proposed implementation under exact-head review
- Repository: `ContextualWisdomLab/noema`
- Initially observed protected base: `8273b350b633eae245f5cab8da4cb1d43799c3a2`
- First integrated protected base: `db4f444c1b1849ec615364a233469870c23407e6`
- Observation date: 2026-08-12 UTC
- Canonical operational contract: `docs/hourly-commercial-readiness-loop.md`
- External scheduler evidence owner: issue #96

This doctoring record distinguishes observed GitHub evidence, source-supported platform behavior, project decisions, and inference. It does not claim that an external ChatGPT task was modified, that the Maintainer App is provisioned, or that a pull request is merge-authorized.

## Observed failure

Scheduled workflow run `31587463951` completed with one job named `commercial-readiness-maintenance`. The job conclusion was `skipped`, its step list was empty, and no runner was assigned. At the observed default-branch source, the sole job had the condition:

```yaml
if: vars.NOEMA_MAINTENANCE_ENABLED == 'true'
```

The connector available to this review could not read the repository variable value because the variables endpoint returned `403 Resource not accessible by integration`. Therefore the evidence supports only this bounded conclusion: the job-level expression evaluated to a non-running state. It does not identify whether the variable was absent, false, inaccessible to the integration, or intentionally disabled.

## Root-cause analysis

### Immediate cause

The activation condition was attached to the only job. When the condition did not permit execution, GitHub had no step in which Noema could retain a reason code, inspect the remaining configuration prerequisites, or upload bounded activation evidence.

### Systemic cause

The design combined two different decisions:

1. whether the scheduler should produce read-only operational evidence; and
2. whether the credential-bearing Maintainer App write lane may run.

The first decision is safe and useful on every schedule. The second must remain fail closed. Binding both to one job-level condition converted an expected external gate into an opaque whole-run skip.

### Secondary confidentiality finding

The first implementation retained one boolean per activation prerequisite and a specific missing-configuration reason. GitHub documents that artifact metadata for public resources can be requested without authentication, and repository readers can retrieve workflow artifacts. Because `ContextualWisdomLab/noema` is public, individual App-private-key, App-client-ID, reviewer-login, or maintenance-variable presence is not suitable for artifact or step-summary retention even when no secret value is printed.

The source configuration names are already visible in the workflow, but whether each credential exists is additional operational information. The public evidence contract was therefore narrowed to repository/run/source identity, `write_ready`, and a configuration-opaque terminal classification/reason.

### What is not established

The observation does not prove a GitHub Actions outage, a malformed secret, an invalid App installation, a reviewer identity mismatch, or a hidden scheduler-provider error code. Those hypotheses require separate access-controlled evidence and must not be invented from the skipped result.

## Remedies considered

| Remedy | Feasibility decision | Reason |
| --- | --- | --- |
| Re-run the unchanged workflow | Rejected | It recreates the same job-level decision and adds no diagnostic boundary. |
| Set `NOEMA_MAINTENANCE_ENABLED=true` immediately | Rejected | It could open a credential-bearing lane before App, reviewer, and governance prerequisites are evidenced. |
| Remove the activation gate | Rejected | It weakens the fail-closed authorization boundary and can turn missing credentials into recurring failures. |
| Add another hourly workflow | Rejected | It creates a duplicate writer/schedule, increases queue pressure, and divides operational authority. |
| Grant the default `GITHUB_TOKEN` write access | Rejected | It expands authority and changes downstream workflow-trigger behavior instead of repairing diagnosis. |
| Publish one boolean or reason per missing credential | Rejected after security review | Public-repository artifacts can expose configuration-presence metadata. |
| Add an always-running read-only activation preflight, then gate the existing write job on its configuration-opaque output | Selected | It preserves one schedule and least privilege while proving whether the write lane was evaluated without publishing which credential or variable is absent. |

## Selected design

The workflow is split into two jobs.

### `activation_preflight`

The preflight has workflow-level `contents: read` authority only and does not checkout repository code, mint an App token, call repository write APIs, use OIDC, or expose secret values. It evaluates internally:

- exact repository identity;
- canonical 40-character workflow source SHA shape;
- explicit maintenance activation;
- presence of Maintainer App client ID, private key, and reviewer login.

It emits `write_ready` and `terminal_classification` as job outputs. The public artifact contains no individual variable/secret presence boolean and no reason that identifies the missing prerequisite. When any ordinary activation prerequisite is unavailable, the public result is the fixed pair:

```text
terminal_classification=EXTERNAL_GATE_REMAINS
reason_code=activation_prerequisite_unavailable
```

Repository/source identity violations remain separately classified because they do not disclose credential state.

### `maintain`

The existing maintenance job declares `needs: activation_preflight` and runs only when:

```yaml
if: needs.activation_preflight.outputs.write_ready == 'true'
```

The preflight output is not merge authority. The maintenance job must still mint the repository-scoped Maintainer App token, run live `main` governance audit, collect full exact-head evidence, and satisfy all existing review and merge gates.

## Classification semantics

| Classification | Meaning |
| --- | --- |
| `EXTERNAL_GATE_REMAINS` | At least one activation prerequisite is unavailable; public evidence does not identify which one. |
| `SAFETY_OR_POLICY_BLOCKER` | Repository or workflow-source identity is invalid. |
| `NO_ACTION_NEEDED` | The write lane may evaluate its existing governance controls. |

`NO_ACTION_NEEDED` deliberately does not mean that any pull request is green, approved, protected, mergeable, releasable, deployed, or acquisition ready.

## Evidence minimization

The activation artifact excludes:

- secret values and private keys;
- existence booleans for individual variables or secrets;
- reason codes naming a missing client ID, private key, or reviewer login;
- GitHub tokens or OIDC material;
- reviewer credential values;
- vulnerability details;
- hidden model reasoning;
- pull-request approval or release claims.

The public artifact retains only what is necessary to prove that the read-only preflight ran, which exact trusted workflow source it evaluated, whether the credential-bearing lane opened, and whether a non-secret safety identity check failed. Exact configuration diagnosis remains in repository administrator controls and access-controlled Maintainer App readiness evidence.

## Test-first evidence

The first regression-only head `4dffd09ddac273a8f8756c0db5f6c9289cd9b861` added `test/hourly-commercial-readiness-activation.test.ts` before the workflow implementation. A focused local contract execution against the fetched predecessor workflow failed at `missing activation_preflight`, which is the intended RED condition. Exact-head GitHub application CI for that predecessor remained queued during the initial implementation window and is not treated as completed RED evidence.

After the public-artifact access review, regression-only head `7bfecff9cc39705fae1e144496ed23298452384f` added a second failing contract: no `AUTH_OR_TOOLING_BLOCKER`, no credential-specific reason, and no per-prerequisite boolean may be serialized into the public workflow evidence. The predecessor workflow contained all prohibited strings, so the contract was RED before implementation commit `3eef1a41a70a35c8ef27a931e1f5ca1aace87b76` replaced them with a generic closed-lane reason and removed individual booleans from the JSON artifact.

The integrated head must still pass the repository-owned application CI, reviewer CI, central Security Scan, coverage gates, current review, and branch-protection requirements. Source-level RED demonstrations do not substitute for those acceptance gates.

## Standards and primary documentation rationale

GitHub job outputs are designed to be consumed through the downstream `needs` context. This supports a narrow preflight-to-write-lane decision without sharing a credential. GitHub also documents that dependent jobs normally do not run when a prerequisite fails or is skipped, which is why the preflight itself must complete successfully for classified external gates.

Workflow artifacts are the platform mechanism for retaining run-generated evidence after a job completes, but GitHub's artifact REST documentation states that public resources may be queried without authentication. This makes a public artifact an unsuitable place for individual credential-presence metadata. GitHub's secrets documentation also recommends minimum credential permissions and explicitly warns against intentional secret exposure. These source-supported platform behaviors inform the configuration-opaque evidence decision; they do not independently prove Noema's configuration is correct.

GitHub recommends granting `GITHUB_TOKEN` only the minimum permissions required and using a GitHub App installation token when different permissions are needed. The workflow therefore retains top-level `contents: read` and mints the repository-scoped Maintainer App token only after the preflight opens the write lane.

## References — APA 7th

GitHub, Inc. (n.d.). *GITHUB_TOKEN*. GitHub Docs. Retrieved August 12, 2026, from https://docs.github.com/en/actions/concepts/security/github_token

GitHub, Inc. (n.d.). *REST API endpoints for GitHub Actions artifacts*. GitHub Docs. Retrieved August 12, 2026, from https://docs.github.com/en/rest/actions/artifacts

GitHub, Inc. (n.d.). *Secrets*. GitHub Docs. Retrieved August 12, 2026, from https://docs.github.com/en/actions/concepts/security/secrets

GitHub, Inc. (n.d.). *Use GITHUB_TOKEN for authentication in workflows*. GitHub Docs. Retrieved August 12, 2026, from https://docs.github.com/en/actions/tutorials/authenticate-with-github_token

GitHub, Inc. (n.d.). *Using jobs in a workflow*. GitHub Docs. Retrieved August 12, 2026, from https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-jobs

GitHub, Inc. (n.d.). *Workflow syntax for GitHub Actions*. GitHub Docs. Retrieved August 12, 2026, from https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax
Loading
Loading