Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Changelog

## Unreleased
- 루트 `README.md`를 구매자/운영자 표면으로 재작성한다. 제품 정의, 단독 실행, 호스트 호출(HTTP API·`contracts/orchestrator-gateway.json`), 운영 설정을 앞에 두고 naruon·곁(gyeot) 허브-리프 호출을 지원 경로로 문서화한다. CloudAgent/OpenCode 절차, PR 스택, exact-head CI, writer/agent 경계는 `CONTRIBUTING.md`와 `docs/development/contributor-and-agent-procedure.md`로 옮기며 삭제하지 않는다. `test/readme-customer-facing.test.ts`가 README에 봇 매뉴얼 잔여가 남지 않는지 고정한다.
- 비리뷰 LLM 작업인 `hourly-product-development`를 리뷰와 동일한 `contextual-orchestrator` 게이트웨이 계약(`NOEMA_LLM_API_URL` `/v1`, 모델 별칭 `contextual-orchestrator`, 전용 `NOEMA_LLM_API_KEY`)으로 전환한다. Llama Nemotron → Nemotron Super → DeepSeek 순차 NIM 후보 폴백과 `NVIDIA_NIM_API_KEY` 직접 호출을 제거하고, 공유 `scripts/verify-orchestrator-gateway.mjs`가 `/healthz` 신원과 직접 공급자 호스트를 실패-폐쇄한다. 리뷰어의 `NOEMA_FALLBACK_*` / PydanticAI `FallbackModel` 순차 폴백도 제거해 남은 설정은 실패-폐쇄한다. 동일 계약을 `contracts/orchestrator-gateway.json`으로 공개해 `ContextualWisdomLab/naruon` 판단·결정 에이전트가 1급 소비자로 재사용할 수 있게 한다. naruon 배선은 별도 저장소 PR이다. 상위 공급자 키는 오케스트레이터 KV에 남기며 OIDC 토큰 중개·App 신원·3-runner 샌드박스 경계는 유지한다.
- 검증된 active-orphan 워크플로 하나를 운영자가 호출할 수 있는 `operations:workflow-registry-disable` 경로를 추가한다. 저장소와 워크플로 ID를 `NOEMA_MAINTAINER_TOKEN_PATH` 위임 토큰 파일 읽기 전에 검사하고, 신선한 전체 레지스트리 감사·즉시 live refresh·프로세스 로컬 plan·보호된 main/워크플로 재검증·사후 전체 감사 봉투(`schema_version` 1, `PASS`/`FAIL`, `remaining_failure_codes`, `remaining_active_orphan_ids`)를 통과한 뒤에만 영수증을 유지한다. 성공 종료와 `post_audit_status: FAIL`은 해당 ID만 `disabled_manually`가 되었고 레지스트리는 아직 더럽을 수 있음을 뜻하므로, 운영자는 영수증의 `remaining_active_orphan_ids`로 다음 단일 호출을 이어간다. 배치 비활성화·자가 수리 워크플로·거버넌스 완화는 추가하지 않으며 호출 계약은 doctoring에 기록한다.
- 읽기 전용 `operations:runner-assignment` audit를 추가해 exact workflow run/source head에 대한 runner assignment를 완전 pagination으로 진단하고, 신선한 unassigned queue는 bounded grace 이후 실패-폐쇄한다. 이 증빙은 runner assignment와 required Check/CI, formal review, merge, release, deployment authority를 분리하며 assigned runner 이후 workflow failure를 성공으로 승격하지 않는다.
Expand Down
50 changes: 50 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Contributing to Noema

This file is for maintainers, contributors, and coding agents. Buyers and
operators should start at [`README.md`](./README.md).

## Development

```bash
npm install
npm test
npm run typecheck
npm run security:scan
```

There is no lint script. `typecheck` and tests are the code gates. Coverage is
scoped to `src/**/*.ts` and CI enforces 100% statements, branches, functions,
and lines.

Update `CHANGELOG.md` (`## Unreleased`) with every behavior change. Docs in
`docs/` and the changelog are largely Korean; code, comments, and this file
are English.

## Where procedure lives

| Topic | Document |
| --- | --- |
| Customer/operator product surface | [`README.md`](./README.md) |
| Cross-agent security, secrets, and LLM gateway rules | [`AGENTS.md`](./AGENTS.md) |
| Bot, writer, and hourly-loop procedure | [`docs/development/contributor-and-agent-procedure.md`](./docs/development/contributor-and-agent-procedure.md) |
| Hourly product-development operations | [`docs/operations/hourly-product-development.md`](./docs/operations/hourly-product-development.md) |
| Hourly commercial-readiness / SHA-bound merge | [`docs/hourly-commercial-readiness-loop.md`](./docs/hourly-commercial-readiness-loop.md) |
| Review sandbox | [`docs/noema-agent-sandbox-plan.md`](./docs/noema-agent-sandbox-plan.md) |

Do not put CloudAgent, OpenCode session, PR-stacking, exact-head CI, or
do-not-merge procedure back into `README.md`. That file must stay
customer/operator facing.

## Relocated hourly product-development summary

`hourly-product-development.yml` runs a proposal-only coding session through
the same `contextual-orchestrator` gateway contract as review
(`NOEMA_LLM_API_URL`, `NOEMA_LLM_MODEL`, dedicated `NOEMA_LLM_API_KEY`) when
the PR queue is empty. It does not iterate a model-candidate list. It cannot
review, merge, release, or deploy; the existing hourly commercial-readiness
loop retains exact-head governance and SHA-bound merge authority.

Full runner isolation, OpenCode pin, publication lease, and credential split:
[`docs/operations/hourly-product-development.md`](./docs/operations/hourly-product-development.md).
Doctoring (APA 7th, existing verified sources only):
[`docs/doctoring/hourly-nim-opencode-development.md`](./docs/doctoring/hourly-nim-opencode-development.md).
231 changes: 158 additions & 73 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,97 +1,182 @@
# Noema

Noema is ContextualWisdomLab's multi-purpose GitHub App bot: an OIDC token
broker, an independent LLM pull request reviewer, hourly product development,
and a first-class agent contract for `ContextualWisdomLab/naruon` judgments
and decisions. naruon wiring is a separate repository pull request; this
repository publishes the reusable gateway contract.

It runs as a Cloudflare Worker on the Free tier:

- GitHub Actions requests a GitHub OIDC token with audience `cwl-noema-review`.
- Noema verifies the OIDC issuer, audience, organization owner, and trusted central workflow identity.
- Noema exchanges the verified OIDC token for a GitHub App installation token scoped to the target repository.
- The central `.github` workflow uses that installation token to submit an LLM review verdict from a GitHub App identity separate from OpenCode Agent.
- The Project #1 review-bot roadmap keeps untrusted code/data analysis in a separate sandbox; see `docs/noema-agent-sandbox-plan.md`.

The LLM call itself is configured in the central workflow with:

- `NOEMA_LLM_API_URL` — the HTTPS `contextual-orchestrator` base URL ending in
`/v1`
- `NOEMA_LLM_MODEL` — normally the gateway routing alias
`contextual-orchestrator`
- `NOEMA_LLM_API_KEY` — a dedicated gateway inference token, never an upstream
provider key

The same contract is reusable by naruon. The secret-free machine-readable
copy is [`contracts/orchestrator-gateway.json`](./contracts/orchestrator-gateway.json);
see [Orchestrator gateway consumer contract](./docs/orchestrator-gateway-consumer-contract.md).
Noema is a ContextualWisdomLab leaf product: a GitHub App credential broker
and an independent LLM pull-request reviewer. It runs on its own, and a host
calls it through a published HTTP API and a secret-free LLM gateway contract.
That hub-and-leaf call is the supported MSA path — **따로 또 같이** — not a
reason to merge repositories.

It deploys as a Cloudflare Worker (Free tier) with two jobs:

1. **Token exchange.** GitHub Actions presents an OIDC JWT (audience
`cwl-noema-review`). Noema verifies issuer, audience, organization owner,
and the exact trusted central workflow identity, then returns a
repository-scoped GitHub App installation token
(`pull_requests: write`, `contents: read`, `checks: read`).
2. **Review.** The default-branch
[`central-review`](./.github/workflows/central-review.yml) runtime accepts a
`noema-review` dispatch and publishes an App-authored verdict. Untrusted
analysis stays in a separate sandbox; see
[`docs/noema-agent-sandbox-plan.md`](./docs/noema-agent-sandbox-plan.md).

Every Noema LLM job — production review, hourly product development, and
host-side judgments — calls `ContextualWisdomLab/contextual-orchestrator`.
Upstream provider keys stay in the orchestrator credential KV. Noema does not
walk a sequential model list or fall back to a direct provider.

## Composition hubs

Leaf products stay independently deployable. Composition hubs call them as
published dependencies. Do not fold Noema into a hub repo.

| Hub | Role | How it calls Noema |
| --- | --- | --- |
| [`naruon`](https://github.com/ContextualWisdomLab/naruon) | Judgments and decisions | First-class consumer of the published orchestrator gateway contract. Naruon wiring is a separate repository pull request. |
| [`gyeot` (곁)](https://github.com/ContextualWisdomLab/gyeot) | On-device wellness composition hub | Call Noema through the HTTP API and/or the same published contract when a host needs token exchange or the LLM gateway. |

The machine-readable LLM contract is
[`contracts/orchestrator-gateway.json`](./contracts/orchestrator-gateway.json).
Narrative: [Orchestrator gateway consumer contract](./docs/orchestrator-gateway-consumer-contract.md).
Print it with `node scripts/verify-orchestrator-gateway.mjs --print-contract`.

The product repository also owns the default-branch-only
[`central-review`](./.github/workflows/central-review.yml) runtime. It accepts a
`noema-review` `repository_dispatch` event containing `target_repository`,
`pr_number`, and the exact `pr_head_sha`; branch-selected manual workflow code
cannot receive the App key. The runtime waits up to 90 minutes for non-OpenCode
checks, initializes and explores CodeGraph at the exact target head, permits a
single provider request to run for 90 minutes, and delegates upstream failover
to `contextual-orchestrator` before publishing an App-authored review. Before
sending the manifest it rejects known direct-provider URLs and verifies the
unauthenticated `/healthz` service identity. It rejects target symlinks, strips
credentials from the CodeGraph subprocess, and revalidates the live head
immediately before publication.

The production cutover is intentionally separate from the code change because
it creates organization variables and a secret. Follow the
[contextual-orchestrator reviewer cutover runbook](./docs/contextual-orchestrator-reviewer-cutover.md);
do not reuse `OPENAI_API_KEY` as Noema's gateway token.

Example dispatch (bind the SHA from a fresh PR read, never from stale local
state):
Host LLM settings (never an upstream provider key):

```bash
gh api repos/ContextualWisdomLab/noema/dispatches -X POST --input - <<'JSON'
{"event_type":"noema-review","client_payload":{"target_repository":"ContextualWisdomLab/example","pr_number":1,"pr_head_sha":"0123456789abcdef0123456789abcdef01234567"}}
JSON
```
| Name | Meaning |
| --- | --- |
| `NOEMA_LLM_API_URL` | HTTPS OpenAI-compatible base ending in `/v1` |
| `NOEMA_LLM_MODEL` | Routing alias, normally `contextual-orchestrator` |
| `NOEMA_LLM_API_KEY` | Dedicated gateway inference token |

## Required GitHub App permissions
`GET <gateway-root>/healthz` must return
`{"status":"ok","service":"contextual-orchestrator"}`. Known direct-provider
hosts are rejected. Leftover `NOEMA_FALLBACK_*` settings fail closed.

Repository permissions:
## Run it alone

- Pull requests: Read and write
- Checks: Read-only
- Contents: Read-only
Requires Node.js 22+ (CI uses Node 24). This package is private; there is no
published npm library. The Worker and the HTTP contract are the product.

Install the app on `ContextualWisdomLab/.github` and target repositories that use the central required workflow.
```bash
npm install
npm test
npm run typecheck
npm run dev
```

## Worker secrets
`npm run dev` starts a local Worker. Provision secrets on the Worker binding
(the KV-equivalent), not `process.env` in `src/`:

```powershell
```bash
wrangler secret put GITHUB_APP_ID
wrangler secret put GITHUB_APP_PRIVATE_KEY_PEM
# optional: pin a single installation instead of discovering by repository
wrangler secret put GITHUB_APP_INSTALLATION_ID
```

Optional:
Deploy:

```powershell
wrangler secret put GITHUB_APP_INSTALLATION_ID
```bash
npm run deploy
```

Runtime guardrail:
Set `NOEMA_EXCHANGE_URL` in `ContextualWisdomLab/.github` (or the customer
central workflow) to the deployed `/exchange` URL. Production cutover that
creates organization variables or secrets is a separate operator step; see
[contextual-orchestrator reviewer cutover](./docs/contextual-orchestrator-reviewer-cutover.md).
Do not reuse `OPENAI_API_KEY` as Noema's gateway token.

- `NOEMA_RATE_LIMIT_PER_MINUTE` defaults to `60`. `/exchange` first applies a SQLite-backed Durable Object fixed-window limit coordinated across Worker isolates, then retains the original isolate-local limiter as defense in depth. Missing or malformed distributed decisions fail closed; see [Distributed rate limiting](./docs/distributed-rate-limiting.md).
- `NOEMA_OIDC_JWKS_CACHE_TTL_SECONDS` defaults to `300`; `NOEMA_INSTALLATION_CACHE_TTL_SECONDS` defaults to `600` to reduce repeated external lookups on hot paths.
- `/exchange` accepts only `POST` (`Allow: POST` on 405), returns standard Bearer challenges on 401, bounds untrusted trace/client headers before reflection or rate-limit keying, validates `target_repository` as a string before GitHub token creation, refreshes OIDC JWKS when a new signing `kid` appears, returns no-store/nosniff response headers, and keeps issued/inbound tokens out of operational logs.
## How a host calls it

## Deploy
The public HTTP surface is documented in [`openapi.json`](./openapi.json) and
[API 명세](./docs/api-spec.md). Every JSON response is
`{ ok: true, data, trace_id }` or
`{ ok: false, error_code, message, details, trace_id }`.

```powershell
npm install
npm run deploy
| Method | Path | Purpose |
| --- | --- | --- |
| `GET` | `/health` | Liveness only. Does not prove credential-exchange readiness. |
| `GET` / `HEAD` | `/ready` | Unauthenticated runtime readiness. Incomplete config returns `503 ERR_SERVICE_NOT_READY` without reflecting secrets. |
| `POST` | `/exchange` | Exchange a GitHub Actions OIDC bearer for a short-lived installation token. |

Example liveness check:

```bash
curl -sS "$NOEMA_BASE_URL/health"
```

Example exchange (OIDC bearer from GitHub Actions; JSON body optional, max
8,192 UTF-8 bytes):

```bash
curl -sS -X POST "$NOEMA_EXCHANGE_URL" \
-H "authorization: Bearer $ACTIONS_ID_TOKEN" \
-H "content-type: application/json" \
-d '{"target_repository":"ContextualWisdomLab/example"}'
```

Set `NOEMA_EXCHANGE_URL` in `ContextualWisdomLab/.github` variables to the deployed `/exchange` URL.
`target_repository` must be a string `owner/name` in the allowed organization.
`/exchange` accepts only `POST` (`Allow: POST` on 405), returns Bearer
challenges on 401 (`invalid_request` vs `invalid_token`), and includes
`Cache-Control: no-store`, `Pragma: no-cache`, `X-Content-Type-Options: nosniff`,
`X-Trace-Id`, and `X-Latency-Ms`. Issued and inbound tokens must not appear in
logs.

A host that only needs the LLM gateway copies
`contracts/orchestrator-gateway.json` (or calls
`node scripts/verify-orchestrator-gateway.mjs --print-contract`) and uses the
same `NOEMA_LLM_*` settings. Do not copy Noema's OIDC broker, GitHub App
identities, or sandbox/runner isolation into the host.

The Python reviewer package (`reviewer/`) is the judgement plane. It consumes a
bounded PR manifest and can publish a `ReviewVerdict`. See
[`reviewer/README.md`](./reviewer/README.md).

## Operator-facing review dispatch

`central-review` is default-branch-only. Bind the SHA from a fresh PR read:

```bash
gh api repos/ContextualWisdomLab/noema/dispatches -X POST --input - <<'JSON'
{"event_type":"noema-review","client_payload":{"target_repository":"ContextualWisdomLab/example","pr_number":1,"pr_head_sha":"0123456789abcdef0123456789abcdef01234567"}}
JSON
```

Runtime isolation, wait policy, and sandbox controls live in
[`docs/noema-agent-sandbox-plan.md`](./docs/noema-agent-sandbox-plan.md) and
[`docs/development/contributor-and-agent-procedure.md`](./docs/development/contributor-and-agent-procedure.md).

## Required GitHub App permissions

Repository permissions:

- Pull requests: Read and write
- Checks: Read-only
- Contents: Read-only

Install the app on `ContextualWisdomLab/.github` and on target repositories
that use the central required workflow.

## Operator configuration

Public Worker vars (defaults in `wrangler.toml`):

| Variable | Default | Role |
| --- | --- | --- |
| `ALLOWED_ISSUER` | `https://token.actions.githubusercontent.com` | GitHub Actions OIDC issuer |
| `ALLOWED_AUDIENCE` | `cwl-noema-review` | OIDC audience |
| `ALLOWED_REPOSITORY_OWNER` | `ContextualWisdomLab` | Allowed org |
| `ALLOWED_WORKFLOW_REPOSITORY` | `ContextualWisdomLab/.github` | Trusted workflow repo |
| `ALLOWED_WORKFLOW_REF_PREFIX` | `…/noema-review.yml@refs/heads/main` | Exact trusted workflow ref (name kept; matching is exact, not prefix) |
| `GITHUB_API_BASE` | `https://api.github.com` | GitHub Cloud API origin |
| `NOEMA_RATE_LIMIT_PER_MINUTE` | `60` | `/exchange` fixed-window budget |
| `NOEMA_OIDC_JWKS_CACHE_TTL_SECONDS` | `300` | OIDC JWKS cache |
| `NOEMA_INSTALLATION_CACHE_TTL_SECONDS` | `600` | Installation-id cache |

`/exchange` first applies a SQLite-backed Durable Object fixed-window limit
across Worker isolates, then keeps the isolate-local limiter as defense in
depth. Missing or malformed distributed decisions fail closed; see
[Distributed rate limiting](./docs/distributed-rate-limiting.md).
OIDC `jti` values are consumed once by a Durable Object replay guard.

## 판매/운영 패키지

Expand All @@ -101,6 +186,7 @@ Set `NOEMA_EXCHANGE_URL` in `ContextualWisdomLab/.github` variables to the deplo
- [온보딩 가이드](./docs/onboarding.md)
- [운영 Runbook](./docs/runbook.md)
- [Distributed Rate Limiting](./docs/distributed-rate-limiting.md)
- [Runtime readiness](./docs/runtime-readiness.md)
- [Hourly Commercial-Readiness Loop](./docs/hourly-commercial-readiness-loop.md)
- [Hourly Orchestrator Product Development](./docs/operations/hourly-product-development.md)
- [SLA/지원 정책](./docs/sla-and-support.md)
Expand All @@ -120,8 +206,7 @@ Set `NOEMA_EXCHANGE_URL` in `ContextualWisdomLab/.github` variables to the deplo
- [Buyer Due Diligence Index](./docs/buyer-due-diligence-index.md)
- [Transfer Readiness Plan](./docs/transfer-readiness-plan.md)
- [Library Boundary Decision](./docs/library-boundary-decision.md)

`hourly-product-development.yml` runs a proposal-only OpenCode session through the same `contextual-orchestrator` gateway contract as review (`NOEMA_LLM_API_URL`, `NOEMA_LLM_MODEL`, dedicated `NOEMA_LLM_API_KEY`) when the PR queue is empty. It does not iterate a model-candidate list. It cannot review, merge, release, or deploy; the existing hourly commercial-readiness loop retains exact-head governance and SHA-bound merge authority.
- [Contributing](./CONTRIBUTING.md)

## KPI 계산

Expand Down
Loading
Loading