CONTRIBUTING.md
Rama main · réplica de lectura del repositorio original.
# Contributing to NovelWorld
NovelWorld accepts focused changes that preserve its product, architecture,
security, and evidence contracts. Read the [documentation index](./docs/README.md)
and [agent instructions](./AGENTS.md) before changing behavior.
## Before you start
- Search existing issues and pull requests before opening new work.
- Roadmap work starts from one approved roadmap issue and must preserve its
scope, non-goals, invariants, acceptance evidence, dependencies, and rollback.
- Report vulnerabilities through the private process in
[SECURITY.md](./SECURITY.md), never a public issue.
- Keep one independently mergeable outcome per pull request.
Write identifiers, comments, configuration, contributor documentation, and
default interface copy in English. Keep the English and Simplified Chinese UI
catalogs complete, including accessible labels and local errors. Preserve
Chinese documentation, literary and parser fixtures, and immutable versioned
provider registrations. Interface language does not change source text or the
language of generated content.
## Development setup
Use current stable Rust, Node.js 26, pnpm, Docker, and Docker Compose.
The locked Rust dependencies require version 1.94.1 or newer.
```bash
git clone https://github.com/<you>/novelworld.git
cd novelworld
docker compose up -d postgres
cargo build --workspace
cd frontend
pnpm install --frozen-lockfile
pnpm dev
```
Copy `.env.example` to `.env` when running individual services locally and
preseed valid PostgreSQL values plus `BOOTSTRAP_L0_COMPLETE=true`. The root
server launchers perform that L0 guide for interactive installs. Never commit
the resulting secrets or provider credentials.
## Change workflow
1. Create a short-lived branch from current `main`.
2. State the problem and acceptance evidence before implementation.
3. Make the smallest change that satisfies the accepted outcome.
4. Add tests at the lowest layer that proves the behavior; add integration or
browser coverage when the contract crosses a process or user journey.
5. Update affected contracts, runbooks, and threat boundaries in the same
commit series.
6. Run the relevant local gates, open a pull request, and respond to review with
new evidence rather than unsupported claims.
### Small-team review
For routine changes, use one non-author agent review of the final diff and its evidence.
One reviewer may cover correctness, security, accessibility, and operational risks.
Use the issue or pull-request body as the plan and review record.
Run affected tests and required CI before merging.
Add a separate plan review for changes to service or trust boundaries, data ownership, migrations, deletion, restore, or release controls.
Paid Diagnostics keep their registered plan-review and authorization rules.
Add another reviewer only when the first cannot assess a material risk.
Agents may execute browser journeys, recovery drills, and evidence checks. Report what they actually tested.
Human decisions remain necessary for legal obligations and actions that require explicit operator authority.
Formal policies that require direct human observations still require those observations.
Keep missing human evidence unverified. Agent review alone does not prove live runtime or provider behavior.
### Work authorization, stopping, and cleanup
Inspect the implementation, callers, contract, and current issue or pull request before editing.
Prefer the smallest root-cause change. Reuse existing code and checks.
Proceed directly with routine scoped work.
Within scope, routine work needs no step-by-step approval.
Allowed work includes the following:
- reading files and making reversible edits
- creating branches and worktrees
- running disposable local tests
- formatting files and running affected checks
- commits, pull requests, and review fixes
- proven-safe post-merge branch and worktree cleanup
For complex work, state the deliverable, evidence entrypoints, allowed actions, and stopping condition.
A paid provider run requires explicit authorization for its exact reviewed registration.
Public deployment, credential disclosure, account funding, and destructive or unrecoverable actions require explicit authority.
A frozen or consumed Diagnostic is terminal.
Do not rerun it.
Do not rewrite its evidence.
Do not refill its budget.
Do not change its thresholds to hide a result.
Preserve unrelated working-tree changes.
Before cleanup, prove merge or patch equivalence, branch and worktree ownership, and the absence of open dependencies.
Complete the requested outcome.
Run affected checks.
Inspect their results.
Fix failures caused by the change.
Repeat affected checks.
Follow [small-team review](#small-team-review). Keep required CI blocking.
Stop when acceptance evidence is met or a concrete external blocker prevents progress.
Report evidence classes separately. State skipped or unavailable evidence.
### Roadmap and GitHub records
The Roadmap owns direction, invariants, horizon order, and exit criteria.
The GitHub Project owns live status, horizon, and priority.
Each roadmap issue represents one independently mergeable outcome.
Add active roadmap issues and pull requests to the Project. Keep their fields current.
Link each roadmap pull request with `Closes #<issue>`.
After acceptance evidence exists, the final commit reaches `main`, and required CI passes, set the Project item to `Done`.
Then close the issue.
A structural child may close while its live-evidence parent stays open.
Use the issue body and Project fields as the current execution record. Replace superseded status in place.
Default to zero status comments. Do not comment on plans, progress, pending CI, or facts already in linked records.
An immutable comment is only for a consumed external/provider execution or a human decision that the issue body and fields cannot represent.
Add one concise immutable comment for that event or decision.
Before deleting an obsolete comment, preserve unique audit and no-rerun evidence.
Before deletion, migrate inbound links.
During the current private rapid-iteration phase,
[human acceptance is not required](./docs/QUALIFICATION_POLICY.md#rapid-iteration-acceptance).
Follow [small-team review](#small-team-review) and all affected automated/required
CI gates. A focused change needs no additional human sign-off.
This applies throughout the active roadmap goal, including agent-adjudicated
new revisions and bounded Diagnostics under the linked policy's pre-call
identity, budget and evidence controls.
Keep unperformed human checks unverified, and do not label iteration delivery
as formal release qualification or authorization to rerun a frozen model.
Use an [architecture decision record](./docs/adr/0000-template.md) when changing
a service boundary, data ownership, trust boundary, public contract,
consistency model, availability target, or irreversible dependency. Routine
implementation choices stay in the pull request.
## Engineering constraints
Backend changes must preserve the private `single-node-v1` Cloud Native, DDD,
and microservice contract:
- Domain layers do not import application, infrastructure, interface, or
concrete adapter types. Application code depends on ports, not adapters.
- External systems are reached through domain ports and infrastructure
adapters.
- Services communicate over HTTP adapters and runtime packages do not depend
on one another. Runtime SQL is checked against the versioned relation-owner
manifest.
- Authoritative facts are externalized; process-local state is disposable
cache/projection/admission only. Each runtime keeps external configuration,
separate liveness/readiness, JSON tracing, metrics, and graceful signals.
- New or changed dependency calls define their deadline and retry behavior;
existing unqualified timeout/drain paths are gaps, not reusable defaults.
- SQL uses parameterized bindings; LLM calls use the shared bounded retry
contract.
`cargo run --locked -p architecture-check -- check` scans the reachable module
graph of all five runtime packages. Production modules and tests nested inside
a DDD layer are checked for layer and SQL violations. Root `cfg(test)`
composition modules are still SQL-scanned but have no DDD layer assignment;
runtime-hook evidence must come from reachable non-test code. The gate blocks
layer inversions, unreviewed domain/application crates and local helpers,
concrete adapter leakage, reviewed non-HTTP/raw transport patterns,
unanalyzable SQL, owner violations, relation/routine inventory drift, cross-owner
view/routine/trigger dependencies, undeclared cross-owner foreign keys, and
missing static runtime hooks. Existing shared-schema debt is exact and visible,
not a general allowlist; adding declared debt is a versioned policy change that
must be justified in review.
Nine existing migrations contain executable `DO` bodies that the conservative
parser intentionally does not interpret. Each is an exact normalized full-file
hash debt; any edit reopens the blocker. New or changed migrations must pass the
strict statement, ownership, view, routine, trigger, and foreign-key audit.
Migration 0002 is strictly scanned and is not part of that hash-debt count.
The unchanged 0031 and 0033 migrations have two additional exact-file debts
because later migrations replace their historical series trigger functions.
The new 0034 definition remains strictly audited against `init.sql`.
Frontend changes must preserve Feature-Sliced Design:
```text
app -> pages -> widgets -> features -> entities -> shared
```
`pages`, `widgets`, `features`, and `entities` are sliced layers. Imports only
point downward, and an import into one of those slices must address its root
`index.ts`/`index.tsx` public API, such as `@/entities/character`; importing a
private `ui`, `model`, `api`, or other path below another slice is forbidden.
Slices in the same layer cannot import one another. A slice may use relative
paths only for its own internals.
Root entry modules may only bootstrap `app`. The non-sliced `app` and `shared`
layers retain the relative imports needed for their own composition and shared
internals. Those exceptions do not permit an upward import or bypassing a
sliced-layer public API. The architecture check scans every TypeScript/TSX
module under `frontend/src`, including tests and source-side mocks, and treats static
and type-only imports, import types, literal dynamic imports, re-exports,
`require`/import-equals, literal Vitest/Jest module APIs (`mock`, `doMock`,
unmocking, and actual/mock loaders), aliases, and relative paths as dependency
edges. There is no legacy allowlist: any violation fails `pnpm lint:fsd` and
blocks merge.
Server state uses TanStack Query, client state uses Zustand, and HTTP/SSE calls
go through `frontend/src/shared/api/client.ts`.
## Verification
Run the narrowest useful check while iterating, then all affected gates before
review.
Push and pull-request CI select jobs from the complete change set:
| Changed area | Selected checks beyond the always-on policy checks |
|---|---|
| Root Markdown, documentation prose/images, issue/PR templates | None |
| Backend services, shared Rust crates, integration tests | Backend, integration, production smoke |
| Frontend | Frontend build/browser, desktop contract, production smoke |
| Tauri shell | Desktop contract and backend (includes Tauri dependency audit) |
| PostgreSQL schema/migrations | Backend, integration, desktop contract, production smoke |
| Launchers | Unix and Windows launcher contracts |
| Shared build/configuration, workflows, or unknown paths | Full suite |
`tools/ci_scope.py` owns the conservative routing rules. Mixed changes take the
union; unavailable Git history falls back to the full suite. Scope self-tests,
specification checksums, and secret scanning always run. The required
`Rust Build & Test` check aggregates policy and selected-job results, so a failed
scope detector or failed selected check blocks merging even when unrelated jobs
are skipped. Manual verification (`make verify`) and reusable release CI run
the full suite. Docker image publication and portable builds retain their
existing release/manual triggers.
### Backend
```bash
cargo fmt --all -- --check
cargo run --locked -p architecture-check -- self-test
cargo run --locked -p architecture-check -- check
cargo check --workspace --exclude integration-tests
cargo test --workspace --exclude integration-tests
cargo clippy --workspace --exclude integration-tests --all-targets -- -D warnings
```
The backend architecture gate is static source evidence. It does not prove
database grants, complete timeout/drain behavior, recovery drills, alerting,
capacity, multi-replica safety, horizontal scaling, or public deployment.
Those claims require their own runtime or migration evidence.
For monitoring configuration or notification changes, run:
```bash
bash infra/monitoring/drill.sh --rules-only
python3 tests/e2e/notification_drill_test.py
python3 infra/monitoring/notification_drill.py
```
The notification check uses four owned disposable containers and pinned images.
It requires a local Linux Docker engine and Linux `iproute2`.
It checks that the application fixture cannot reach the Alertmanager API.
It tests retries, timeout handling, restart deduplication and resolved notifications.
It removes only its project resources and makes no real receiver or provider call.
It does not qualify operator receipt, paging or journey SLOs.
Changes to diagnostic LLM dispatch, its owner ledger, or lifecycle fixture also
run the offline budget boundary gate on Linux with Docker and OpenSSL:
```bash
cargo build --locked -p user-service -p novel-service -p agent-service -p narrative-service
cargo build --locked -p llm-client --example diagnostic_budget_driver
python3 tests/e2e/diagnostic_budget_lifecycle_test.py
python3 tests/e2e/diagnostic_release_docker_spy_test.py
python3 tests/e2e/diagnostic_journey_test.py
python3 tools/llm-budget/test_verify.py
python3 - <<'PY'
import runpy
import subprocess
fixture = runpy.run_path("tests/e2e/diagnostic_budget_lifecycle.py")
for name in ("PYTHON_IMAGE", "PG_IMAGE", "REGISTRY_IMAGE"):
subprocess.run(["docker", "pull", fixture[name]], check=True, timeout=180)
PY
python3 tests/e2e/diagnostic_budget_lifecycle.py \
--owner-binary target/debug/user-service \
--client-binary target/debug/examples/diagnostic_budget_driver
```
Images are fetched before isolation. The fixture packages the four normal
service binaries (from the same directory as `--owner-binary`) without source
or credentials, uses a temporary loopback-only registry for real repository
digests, and invokes the existing release capability preflight unchanged.
The budget/provider test uses synthetic credentials, real PostgreSQL and
production client constructors on a unique Docker internal network; it never
loads an operator key or forwards to a provider. It removes only its own
containers, anonymous volumes, image references and network, including on
ordinary cancellation (not SIGKILL); shared base images/build cache remain.
On hosts with exhausted Docker address pools,
`--subnet` accepts an unused RFC1918 `/28` after Docker/host-route overlap checks.
The same fixture executes real `release.sh`, Compose config/pull and digest
probes against an explicitly synthetic Git/release-state fixture. Invalid
candidate, current and previous artifacts must fail before any deployment
command is attempted, preserving the ledger, running owner/PostgreSQL and
provisioning marker. A command spy forwards permitted operations to real Docker
and refuses unexpected deployment commands; it never substitutes fake success.
Failed preflight leaves Git at the target metadata commit. A subsequent valid
`preflight` succeeds without restoring that checkout; this is safe reentry,
not automatic checkout recovery.
This proves release/capability refusal, dispatch/accounting and owner recreation
boundaries, not release-built artifacts, a supported base-to-candidate release
upgrade or live-model qualification.
Native Rust release images require source-to-binary freshness inside the
existing locked BuildKit cache. Before release compilation, clean only workspace
release artifacts with `cargo clean --locked --release --workspace`; retain
third-party compiled dependencies and registry/layer caches, and never prune a
shared host Cargo target. Historical git-archive source mtimes can otherwise
make Cargo reuse workspace binaries from another source revision, so a completed
Docker `RUN` is not proof that changed source was compiled. Require build evidence
that Cargo compiled the changed source. For a registered release pair, a genuine
B runtime change must yield at least one affected application image with both a
different image ID and at least one different filesystem layer. Docs/cache-only
changes and unchanged runtime inputs need not change every image or binary.
The historical C→B Diagnostic path used merged A (#428),
cache-freshness C (#432), and remote-profile B (#429); eligibility required an
actual merged C baseline and strict-descendant B candidate with the guard on
both sides.
For a prospective V7 source pair and its artifact, prestart, scan, budget, and
authorization gates, follow the [Qualification Policy](docs/QUALIFICATION_POLICY.md)
and the current execution record in [issue #378](https://github.com/Wisdoverse/novelworld/issues/378). Independent review applies only
to the recorded source pair and inputs. A change requires a new review. Source
eligibility does not transfer historical evidence, renew a frozen or consumed
registration, or claim live or formal qualification.
For V7 web frontend inputs, use the Qualification Policy's input classification
and byte-change rules. Unchanged frontend bytes require independent review of
identical active web inputs, complete compiled and served maps, and HTTP
equality. The policy permits exact desktop-path or test-package exclusions only
with complete consumer proof. Record every package, manifest, and lock change.
Unknown use blocks exclusion. Directory and whole-lockfile exemptions are
forbidden. A development-dependency label is not proof. Keep required dependency and
desktop CI, scans, SBOMs, and risk review. Active web-input changes require
different full compiled and served maps and the corresponding frontend layer.
A Rust change cannot bypass this requirement. Apply exclusions only in fresh
exact-pair reviews under the merged policy revision. Historical artifacts and
Frozen records retain their original rules, identities, and results.
For Vision journey tooling changes, also run
`python3 tests/e2e/live_deepseek_journey.py --self-test`. CI runs
`diagnostic_journey_test.py` with the digest-pinned `test-postgres` service and
destroys its volume afterward. The PostgreSQL selector test mutates public rows
and therefore requires `NW_H4_TEST_POSTGRES_DISPOSABLE=1`; never point it at a
shared database. The suite checks single-start registration, committed source
identity, synthetic receipt reconciliation, cancellation, bounded terminal
handling, public-report privacy, and latest-Canon player-entry selection without
a provider call. These checks do not replace the mandatory real release-image
cold-adoption/Settings and lifecycle wiring evidence tracked in #322. No
protected operator key is needed or authorized by these gates.
The same lifecycle fixture has a separate `--journey-images` mode for actual
cold-adoption wiring. Its JSON map contains the four paying services plus
`gateway` and `frontend`, all built with the repository's release Dockerfiles.
This Linux-only mode requires `socat` and an explicitly selected unused RFC1918
`/28` subnet for static loopback ingress. Docker requires an explicitly configured
network subnet for a static container IP; an automatically allocated default
pool is insufficient. The fixture rejects overlap with Docker networks or host
routes. The subnet below is an example; select a free subnet on your host.
Use a pre-created mode-0700 output directory outside the checkout:
```bash
python3 tests/e2e/diagnostic_budget_lifecycle.py \
--journey-images /private/path/six-release-images.json \
--journey-output /private/path/empty-cold-adopt-evidence \
--subnet 10.254.241.0/28
```
This mode reuses the internal-network TLS fixture and supported `release.sh`
cold adoption. Only a private synthetic checkout changes Compose network/CA/
proxy wiring. It assigns nginx a checked unused internal IPv4 address and removes
its Docker published port; a host `socat` listener starts on the random loopback
port before adoption, forwarding only to that nginx address on port 80. The
fixture verifies the actual nginx address after adoption and stops the exact
forwarder's process group on success or failure. This avoids relying on Docker
internal-network port publishing without giving product containers public egress.
Runtime images and release implementation are not replaced.
Private output retains forwarding-process recovery metadata and the pre-adoption
Docker inventory. A failed stop gets one bounded cleanup retry; unproven removal
still fails the fixture. Production Compose Smoke runs this gate after the
existing release-image dispatch/refusal matrix, without publishing private output.
The optional `--cold-report-dir` accepts a separate empty mode-0700 directory
outside the checkout and private evidence tree. It retains only fixed stage,
shutdown and service-state summaries, including bounded PostgreSQL startup hints.
There are at most six exclusive JSON files, each limited to 16 KiB.
CI uploads these safe summaries only when the cold-adoption step fails.
Cold-adoption stdout reports only the zero/nonzero case and fixed boolean stage
presence/terminal flags after the journey's terminal handling, including failure;
private reports and identifiers
remain local. These flags do not replace the fixture's blocking assertions.
The summary also maps the first preserved failure code to fixed allowlist
booleans, marks an unclassified string code, and reports whether base release
timings were recorded; it never emits failure-code strings or timing values.
A second fixed boolean summary recognizes release phase markers and selected
failure classes from at most 1 MiB of the private adoption log. Missing or larger
logs do not produce phase evidence; raw lines and unknown values are never emitted.
Failed adoption also observes the seven fixed application containers before
terminal cleanup: exact name/project ownership precedes ID-addressed log reads.
One 15-second deadline bounds all reads (at most two seconds and 64 KiB per call,
80 log lines per container). Only fixed state fields, health enums and startup
error-word booleans reach stdout; missing containers and unproven observations
are distinct. Error-word matches are hints, not root-cause proof. Observation
failure cannot replace the adoption failure or skip terminal cleanup; cancellation
does not start this observation. No raw container logs are published or retained.
Outer fallback cleanup shares a 60-second deadline, with Docker calls capped at
10 seconds each. CI sends a soft TERM after 15 minutes, reserving 10 minutes for
terminal handling before a hard kill; SIGKILL/host loss still has no cleanup
guarantee and never makes a failed registration resumable.
Exact owned-container removal also removes its anonymous volumes (including
the PostgreSQL image's parent mount). Named PostgreSQL volumes remain governed
by the independent durable-evidence check; no volume pruning is used.
Separate zero/nonzero registrations exercise Settings, the runner's generated
environment, consistent owner/PG checkpoints and restart persistence. Since
these are partial fixtures, the full journey's missing-quality-sample gate
must fail; terminal handling preserves the owned PG volume. After checking
the durable receipts and exact retained-volume ownership, the fixture performs
non-paid cleanup of that volume. Private evidence remains in the output
directory. A single-image cold adoption is not a genuine version upgrade or
a live-model/whole-journey PASS. Run the existing `--runtime-images` matrix
separately as well; cold adoption does not replace its dispatch/ACK-loss cases.
Production Compose Smoke also invokes the same fixture with
`--runtime-images <json-file> --client-binary <path>`: a map from each of the
four service names to its existing local image built by `Dockerfile.rust-service`.
This repeats the full dispatch, owner-recreation and release-refusal matrix
without building replacement service images. Published test-local digests must
retain the original image IDs, and cleanup preserves the source references.
For a narrower capability-only diagnostic, `--capability-images <json-file>`
does not provision a database or start the product. Neither mode proves a
successful different-version upgrade/rollback journey; that remains #230.
### Frontend
```bash
cd frontend
pnpm install --frozen-lockfile
pnpm audit:dependencies
pnpm type-check
pnpm lint
pnpm lint:fsd
pnpm test
pnpm build
```
`pnpm lint:fsd` proves only the statically detectable import boundary contract.
It does not prove that a feature has the correct semantic owner, that runtime
loading succeeds, or that user-visible behavior is correct; type, unit, build,
and applicable browser gates remain required.
Run `pnpm exec playwright test` for user-flow, responsive, or accessibility
changes. Run the PostgreSQL/Redis integration suite when changing persistence,
migrations, caching, or cross-service data contracts:
```bash
docker compose -f docker-compose.test.yml up -d --wait test-postgres test-redis
docker compose -f docker-compose.test.yml run --rm test-migrate
cargo test -p integration-tests
TEST_DATABASE_URL=postgres://test:test@localhost:25432/novelworld_test \
cargo test --locked -p novel-service --test community_series_http -- --ignored
TEST_DATABASE_URL=postgres://test:test@localhost:25432/novelworld_test \
cargo test --locked -p novel-service --test world_source_http -- --ignored
TEST_DATABASE_URL=postgres://test:test@localhost:25432/novelworld_test \
cargo test --locked -p novel-service --test import_metrics -- --ignored
docker compose -f docker-compose.test.yml down -v
```
The production-shaped development base uses PostgreSQL-backed cache mode and
does not start Redis. To exercise the optional production Redis projection,
set `CACHE_MODE=redis`, a strong URL-safe `REDIS_PASSWORD`, and the matching
`REDIS_URL`, then add `--profile redis`; the root launchers derive these
together and are the supported path. The independent integration Compose file
above always starts its isolated unauthenticated test Redis.
### Disposable PostgreSQL recovery drill
The backup/restore drill uses a local E2E stub for model, embedding, and image
requests. It needs no external provider credentials. The drill runs destructive
`down -v` steps, so use a disposable checkout with no private `.env`, synthetic
secrets, and a Compose project that owns no retained data. Select unused
loopback ports before starting.
```bash
set -euo pipefail
suffix="$(date -u +%Y%m%d%H%M%S)-$$"
export COMPOSE_FILE=docker-compose.yml:docker-compose.e2e.yml
export COMPOSE_PROJECT_NAME="nw-recovery-$suffix"
export CONTAINER_PREFIX="nw-recovery-$suffix"
export NGINX_HTTP_BIND=127.0.0.1 NGINX_HTTP_PORT=28080
export E2E_GATEWAY_PORT=28081 E2E_LLM_STUB_PORT=28082
export E2E_API_URL="http://127.0.0.1:${NGINX_HTTP_PORT}/api"
export E2E_LLM_STUB_URL="http://127.0.0.1:${E2E_LLM_STUB_PORT}"
export POSTGRES_IMAGE='pgvector/pgvector:pg18@sha256:2ba9ca5f2e7daa0f0e7723cba1ee9167bab54efd3640516a44ac1a928dd67e7a'
export POSTGRES_PASSWORD="$(openssl rand -hex 32)"
export JWT_SECRET="$(openssl rand -hex 32)"
export RUNTIME_CONFIG_KEY="$(openssl rand -hex 32)"
export INTERNAL_SERVICE_TOKEN="$(openssl rand -hex 32)"
export LLM_API_KEY="$(openssl rand -hex 16)"
# Optional cross-image restore: use an immutable digest already cached locally.
# export E2E_RESTORE_POSTGRES_IMAGE='registry/image@sha256:<digest>'
docker image inspect "$POSTGRES_IMAGE" >/dev/null
[ -z "${E2E_RESTORE_POSTGRES_IMAGE:-}" ] || docker image inspect "$E2E_RESTORE_POSTGRES_IMAGE" >/dev/null
docker compose up -d --wait
tests/e2e/backup_restore_drill.sh
docker compose down -v
```
Run the backup/restore drill only in this owned disposable project. Stop and
preserve the stack if any command fails. The command
does not prove image compatibility, deployment adoption, or the RTO target;
those claims require the corresponding completed native evidence.
The required Compose core journey admits source chapter 2 into an existing
source-1 world without provider work, commits a subsequent event outcome,
checks exact source replay after service restart, and stops Agent during a world turn and checks
durable pending state, the no-overtake barrier, scanner recovery before replay,
and exact replay without another model call. Changes to its script trigger
these drills on PRs. This fixture-based check does not prove live model quality
or release-upgrade recovery. Run it only in the existing isolated CI topology,
not against a personal deployment; the script uses fixed test container names.
The authoritative required gate is [CI](./.github/workflows/ci.yml). To dispatch
that exact workflow for a clean, pushed commit and wait for the result, run:
```bash
make verify
```
This command requires authenticated GitHub CLI access and intentionally creates
one workflow run. It fails closed for dirty, detached, untracked, or unpushed
checkouts.
## Review standard
A pull request is ready for review when it explains:
- the user or operational problem and what is deliberately out of scope;
- externally visible behavior and compatibility impact;
- security, privacy, data, reliability, accessibility, and cost risks;
- automated and manual evidence, including important negative cases;
- rollout, abort signals, rollback or forward recovery;
- monitoring or logs used to detect failure;
- documentation changed, or why no document is affected.
Use `N/A` with a reason instead of omitting a category. Screenshots are required
for visible UI changes. Database migrations must be forward compatible with the
release and rollback procedure in [DEPLOY.md](./DEPLOY.md).
Reviewers block changes that violate architecture boundaries, weaken a contract
without an approved replacement, claim unsupported behavior, or lack evidence
proportional to risk. `Done` means the final commit is merged to `main` and the
required CI is green.
## Documentation
Use the [documentation index](./docs/README.md) to find the owner. Update the
existing contract or procedure that owns a behavior instead of copying it into a
new guide. State each material document’s audience, owned decision, profile,
evidence, and failure boundary; update behavioral documentation in the same PR.
Keep normative targets, current support, and evidence distinct; link to code,
tests, migrations, and versioned records rather than repeating them. Use
repository-relative links and concrete commands. Do not promote a target or
roadmap item to current support without its required evidence.
Use an [ADR](./docs/adr/0000-template.md) only for durable service-boundary,
ownership, trust, public-contract, consistency, availability, or dependency
decisions. Land an accepted ADR with its implementation; routine choices stay
in the PR.
### English technical writing
Use [ASD-STE100 Issue 9](https://www.asd-ste100.org/about_STE.html) as a writing
reference. Read its [FAQ](https://www.asd-ste100.org/STE_faq.html). Use the
[downloads page](https://www.asd-ste100.org/STE_downloads.html) to request the full
standard and dictionary.
Apply these project rules to English technical documents, comments, default interface
instructions, and GitHub records. Preserve Chinese documentation, translations,
literary and parser examples, and story-generation language.
- Use at most 20 words per instruction sentence and 25 words per descriptive sentence.
- Use short sentences and active voice. Give each sentence one purpose.
- Write procedures as commands. State required conditions before actions. Give
each step one action.
- Use fixed technical terms. Define each term when you first use it. Use an
approved word only with its approved meaning and part of speech.
- Use American English. Include the articles and words that complete each sentence.
- State what failed and what the reader should do next in each error message.
- Keep API names, protocol values, and commands exact. Do not paraphrase them.
- State what evidence proves and what it does not prove. Do not call skipped
evidence a pass.
Before you claim full ASD-STE100 compliance, do a review against the full standard
and dictionary. Review existing text before you claim that it obeys ASD-STE100.
Keep all repository safety, architecture, approval, budget, test, and release rules.
## Commit messages
Use [Conventional Commits](https://www.conventionalcommits.org/):
```text
feat: add source-bounded lore retrieval
fix: commit chat turns before emitting done
docs: clarify restore evidence
refactor: consolidate provider retry policy
test: cover import lease recovery
chore: update approved dependencies
```
## License
By contributing, you agree that your contributions are licensed under the
[MIT License](./LICENSE).