This directory composes and verifies existing RefInvest services. It does not own product contracts, domain rules, or service business code.
../contextowns ADRs, domain documentation, and OpenAPI specifications.../api/refinvestowns Core API behavior and its database tables.../compute-api/refinvestowns Compute behavior, DatasetSnapshots, and its database tables.../web/webowns browser behavior and only calls Core API.- This workspace owns local process composition, non-production fixtures, and cross-service smoke tests.
context is the normative source. compatibility.local.yaml only records a
specific implementation combination that was tested against it.
Core writer에는 0~1023 범위의 SNOWFLAKE_NODE_ID를 명시해야 한다. .env.example과 CI 환경은 primary에 0을
사용한다. 기존 .env.local도 이 값을 추가해야 하며 동시에 실행하는 writer에는 서로 다른 값을 배정한다.
전체 Trading fixture의 secondary와 canary는 Spring 설정으로 각각 1과 2를 사용한다.
프로세스 identity는 Spring JSON override를 반영한 실제 node ID를 포함한다. Node ID가 바뀌면 기존
프로세스 재사용을 거절하므로 소유한 프로세스를 중지한 뒤 새 설정으로 시작한다. Harness는 다른 호스트의
중복 할당을 탐지하지 않는다. 배포·rollback의 writer 할당 절차는 Core의 engineering/OPERATIONS_CONFIGURATION.md를 따른다.
Core의 schema 전환 PR #106은 전체 JPA schema를
Flyway V1~V21로 생성하고 Hibernate validate로 기동한다. 일반 API/Web smoke는 이 자동 기동 경로를 사용한다.
Trading fixture runner는 SQL을 직접 적용하는 별도 경로이며, 모든 feature persistence 모듈의 migration을
숫자 순서로 적용·추적한 뒤 해당 fixture에서만 spring.flyway.enabled=false를 설정한다. Core의 기본
Hibernate validate는 유지한다. 별도 canary와 pre-pin migration rehearsal도 같은 migration 집합을 사용한다.
Flyway 이력을 임의로 만들어 이미 적용한 SQL을 감추지 않는다.
python -B -m unittest discover -s scripts -p test_core_migrations.py는 PowerShell/Python의 동일 탐색 순서와
중복 version·잘못된 이름·빈 집합의 거절을 검증한다. 이 검사는 계약 CI에도 포함한다.
API smoke workflow의 수동 실행에서 다섯 candidate ref와 trading_schema_regression=true를 지정하면
별도 Linux job이 전체 Trading raw-policy·canary·migration replay를 실행한다. 기본 PR/API/Web 검증 범위는
유지한다. 프로세스 제어는 Windows PowerShell/pwsh와 소유권을 확인한 플랫폼별 종료 경로를 사용한다.
증적 artifact에는 로그와 evidence/process-control JSON만 포함하며 환경 파일과 keystore는 포함하지 않는다.
scripts/test-trading-normalization-runtime-contract.ps1는 Core public API의 합성 CSV
업로드와 Validate 명령에서 시작하여 durable dispatch, 실제 Compute HTTP DTO, worker,
terminal payload까지 검증한다. Context OpenAPI와 Core·Compute·Web snapshot, Context
identity fixed vector의 byte equality 및 canonical UTF-8/SHA-256 검사는 프로세스를
시작하기 전에 자동 실행된다. Core LedgerRevision publication은 범위에 포함되지 않는다.
.\scripts\verify-contract-sync.ps1 -IncludeWeb
.\scripts\verify-trading-review-fixtures.ps1 -Dataset raw-v3
.\scripts\test-trading-normalization-runtime-contract.ps1
# 기본 포트가 사용 중이면 명시적으로 변경한다.
.\scripts\test-trading-normalization-runtime-contract.ps1 -CorePort 8180 -ComputePort 8181기본 sibling checkout은 ../context, ../api/refinvest, ../compute-api/refinvest,
../web/web이다. 다른 위치에서는 -ContextPath, -CorePath, -ComputePath,
-WebPath를 지정한다. 로컬 PostgreSQL의 전용 DB refinvest_integration과
.env.local의 REFINVEST_INTERNAL_API_KEY가 필요하다. 스크립트는 DB 이름과
loopback 접속 대상을 확인하고, Core는 public, Compute는 실행마다 생성하는
tnorm_* schema를 사용한다. 합성 Artifact는 Core의 로컬 암호화 raw storage에
업로드되며 Compute는 Core가 발급한 read grant URL로 받는다. 외부 storage와
Binance 네트워크는 사용하지 않는다.
검증 범위는 정상 identity·hash·VersionSet 왕복, terminal payload OpenAPI,
locator refresh, 입력 hash 불일치, 지원하지 않는 hash 버전, source bytes hash
불일치, idempotency conflict, Compute API 재시작 후 persistence와 replay다.
locator refresh는 worker를 멈춘 사이 이 실행의 Compute schema에서 최초 grant
revision의 만료 시각만 조정하는 test fault를 사용한다. 이후 Core가 실제 새 grant를
발급하고 같은 job이 완료되는지 확인한다. 종료 시 시작한 프로세스를 정리하며
DB와 로그는 .integration-data/에 보존한다. 실패하면 non-zero로 종료하고
evidence.json, 로그 경로, Core Session·Run·Compute job ID와 안전한 오류 코드를
출력한다. 해당 디렉터리와 .env.local은 커밋하지 않는다.
TR-ACC-IMPORT-QUALITY-001도 같은 runtime script에서 검증한다. 비민감 synthetic Trade
History에서 필수 Maker column만 제거해 Core upload/validation부터 Compute FAILED와
Core terminal polling까지 실행한다. 공개 REJECTED 응답의 qualitySummary: null, safe
failure code, LedgerRevision·ACK 미생성 및 Book latest pointer 불변을 실제 HTTP/DB 경로에서 확인한다.
scripts/test-trading-normalization-publication.ps1는 위 runtime의 환경 구성과 프로세스
helper를 재사용해 정상 결과의 Core publication까지 검증한다. 로컬 PostgreSQL과 .env.local이
필요하다. 기본 sibling checkout 경로는 위와 같고 -ContextPath, -CorePath,
-ComputePath, -WebPath, -EnvironmentFile, -CorePort, -ComputePort,
-TimeoutSeconds를 지정할 수 있다.
.\scripts\verify-contract-sync.ps1 -IncludeWeb
.\scripts\test-trading-normalization-publication.ps1
# 기본 포트가 사용 중인 경우
.\scripts\test-trading-normalization-publication.ps1 -CorePort 8380 -ComputePort 8381비민감 synthetic Binance Trade/Position History 두 파일을 Core public API에 업로드하고
validation, durable dispatch, Compute worker terminal COMPLETED, Core Session ACCEPTED,
Run·dispatch PUBLISHED, LedgerRevision 정확히 하나, Book latest pointer를 확인한다.
Context fixed vector self-test와 실제 terminal bytes SHA-256, 10개 logical count, 9개
section digest, manifest/result hash를 독립 계산한다. 발행된 canonical record, Evidence,
ReviewUnit, allocation, quality/capability/manifest, DB count와 staging cleanup을 검사한다.
Core가 RESULT_READY에 도달한 뒤 publication 잠금을 잠시 잡아 commit된 streaming
staging의 identity·section count도 terminal descriptor와 직접 비교한다.
Compute API 중단으로 ACK의 일시 실패를 만들고, Revision 보존과 ACK 재시도·완료를 검증한다.
추가 polling 후 중복 Revision, record, allocation이 없는지도 확인한다.
실행별 안전한 상태·count·hash prefix와 로그 경로는
.integration-data/trading-normalization/<runId>/evidence.json에 남긴다. 실패는 non-zero다.
전체 crash recovery 조합과 limited-heap stress 검증은 후속 작업이다. 이 fixture는
고정된 source ID·timestamp를 사용하지만 Core가 HTTP 생성 시 부여하는 Book/Artifact ID가
inputSetHash에 포함되므로 실행 간 전체 resultHash 값은 고정되지 않는다. 매 실행마다
Context 규칙으로 실제 hash를 독립 재계산해 일치 여부를 검증한다.
.\scripts\test-trading-normalization-result-version-pin.ps1 -CorePort 8680 -ComputePort 8681기존 runtime/publication harness와 동일한 경로·환경·DB parameter를 받는다. 첫 실행 검사는
verify-contract-sync.ps1 -IncludeWeb이며 DB는 loopback의 refinvest_integration만 허용한다.
Core 공개 합성 Binance 업로드·validation부터 실제 durable create, accepted/status,
terminal descriptor/payload, LedgerRevision 및 ACK까지 V1/V1 pin을 대조한다.
기존 V1 fixed vector, 9개 section digest, 단일 publication과 ACK 실패·재시도를 재사용한다.
loopback Integration 프록시가 ACK 요청 시 별도 DB connection에서 publication commit의
가시성까지 확인한다. 이번 합성 작업의 ACK만 일시적으로 HTTP 503으로 거절한 뒤 해제해
실제 Core outbox 재시도와 Revision 불변을 검증한다. 프록시는 capability URL·token·CSV body를 evidence에 저장하지 않는다.
추가 검증은 ORCH-024(같은 key/request replay·다른 쌍 충돌), 025(3개 필드 누락, 2개 교차 쌍, 3개 미지원 값), 026(descriptor와 payload 불일치 각각)을 포함한다. 부정 응답은 해당 합성 Session으로만 주입하고 실제 서비스 코드는 변경하지 않는다. payload fault는 transport 길이·SHA-256을 맞춰 Core의 pin gate에 도달시킨다. 실패 Session의 Revision·PERSISTED ACK 0건과 기존 Book latest 보존을 확인한다.
ORCH-027은 새 tnorm_legacycore*/tnorm_legacycompute* schema에 실제 pre-pin migration을
적용하고 이번 합성 publication만 복제한다. pin이 없는 Run/dispatch/Job과 옛 request fingerprint를
준비한 뒤 실제 Core V9·Compute 0017 SQL을 실행한다. 이후 두 API를 해당 schema로 재시작해
명시적 V1/V1 HTTP status·replay와 공개 validation replay, 기존 Revision·ACK 불변을 확인한다.
실행 중인 원본 schema의 컬럼이나 trigger를 제거하지 않으며 schema·로그는 조사용으로 보존한다.
결과는 .integration-data/trading-normalization/<runId>/evidence.json의 versionPin에
시나리오별 실행 상태와 비민감 Run/Job/Revision/ACK ID로 남긴다. 재시작 전 로그는 같은 실행
디렉터리의 before-migration-logs/에 보존한다. ORCH-028은 두 Core 인스턴스의 admission/create
중지, claim/send 경쟁, 재시작, 불명확 create 대조, terminal·삭제·취소·기존 ACK 재시도와
격리 canary의 V1/V1 HTTP 검증 후 재개를 확인한다. 필요한 Core V10/control이 없는 조합은
PASS가 아니다. 실행별 Core·Compute schema를 새로 만들며 실패 시 중지·보류 상태를 보존한다.
현재 삭제 후보 조합은 queued Job과 생성 응답을 잃은 Run을 실제 Core DELETE로 제거한다.
runtimeDeletion 증적은 삭제 요청의 COMPLETED, component 체크포인트 6개, 원본 파일·primary·Compute Job
부재, capacity 반환과 같은 요청의 늦은 create 거절을 함께 확인한다. 취소 접수나 404만으로 통과하지 않는다.
프로세스 제어 실패는 process-control/에 종료 코드와 credential을 마스킹한 stdout/stderr를 남긴다.
V2 Core publication은 이번 범위에서 제외한다.
정확한 assertion, 공동 전환에 필요한 후속 기능과 V2 선행 구현 상태는
결과 버전 pin E2E 문서를 참조한다.
scripts/test-trading-raw-policy-clocks.ps1은 Core V16 정책 시계와 expiry 구현이 필요하다.
현재 count assertion에는 Core PR #105의
V17 요청 시점 count snapshot 구현도 필요하다. 실제 publication이 있는
Book 삭제와 두 Book을 포함한 Account 삭제에서 일곱 count·snapshotStatus를 검사하고 purge 후 같은 키로
DELETE를 replay해 원 snapshot이 보존되는지 확인한다. Session/raw-only count도 함께 검사한다.
공개 삭제 상태 assertion에는 Core PR #104의
5c-1 checkpoint 기반 deletion projection도 필요하다.
Core PR #103에 해당 구현과 배포 절차가 있다.
기존 result version pin E2E에 선택형 assertion을 추가하며 기본 runner의 검증 범위는 바꾸지 않는다.
.\scripts\test-trading-raw-policy-clocks.ps1 -CorePort 8680 -ComputePort 8681실제 publication의 accepted 시각과 7일 raw 기한, validation replay의 기한 불변을 검사한다.
실제 Compute worker가 중지된 구간에서 queued Job과 생성 응답 유실을 만든 뒤 synthetic artifact의
raw_expires_at만 현재 시각으로 앞당긴다. Session/Run/generation/request/Compute 상태나 완료 증거를
SQL로 만들어내지 않는다. 실제 expiry worker의 재업로드 필요 전이, 단일 raw-only 요청, runtime 부재
receipt, 파일·key 부재, capacity 반환과 늦은 create 거절을 검증한다. Session metadata는 유지되어야 한다.
공개 응답도 실제 checkpoint에 맞는 primary/object/runtime 상태를 반환해야 한다. Raw-only backup은
NOT_APPLICABLE과 null due date, subtree backup은 별도 부재 증거 없이 IN_PROGRESS를 유지해야 한다.
이 공개 상태 검사는 선택형 raw-policy runner에만 적용하며 기본 E2E 범위는 유지한다.
중지·재개 검증 뒤 accepted raw도 같은 방식으로 만료시켜 기존 Revision·record·evidence·분석 입력과 Book이
그대로인지 확인한다. 증적은 evidence.json의 rawPolicyClocks에 기록한다. 실제 15분 결과 lease의 만료를
기다릴 수 있도록 기본 대기 한도는 1200초이며, assertion이나 제품 lease를 줄이지 않는다.
추가 TTL 대기 동안 이미 실패한 합성 ACK의 next_attempt_at만 충분히 미래로 두고, 중지 검증에
도달하면 즉시 해제한다. 제품의 retry 예산을 바꾸지 않으며 기본 runner의 20분 대기도 유지한다.
ACK 대기 예약은 실제 실패 전송의 lease가 해제된 뒤 조건부 UPDATE로 수행해 진행 중 재예약과
경합하지 않도록 한다. 상태·attempt·lease를 강제로 바꾸지 않는다.
Bootstrap psql은 SQL 전송 전 초기 연결이 Windows 10014로 실패한 경우에만 최대 3회 재시도한다.
SQL 오류·실행 중 연결 단절은 재시도하지 않는다. python -m unittest discover -s scripts -p test_psql_connection_retry.py
로 네 subprocess 분류와 option/SQL 인자·종료 코드 보존을 검증한다.
The deterministic ten-year, five-asset synthetic fixture, six benchmark scenarios, expected-result
review, and baseline protocol are documented in performance/README.md.
They are separate from the short API-smoke fixture. Check wiring with
.\scripts\run-performance-baseline.ps1 -Mode Smoke; use -Mode Official only for the fixed,
long-running protocol. Absolute timing reports remain under .integration-data/.
Use host-native Prometheus and Grafana on Windows. This keeps the Compute API and
Worker metrics listeners on their required loopback addresses; a Docker container's
localhost cannot scrape the host's 127.0.0.1. Install Prometheus and Grafana
locally, then pass their executable paths to the launch script. The existing
PostgreSQL Compose service is unchanged. Prometheus and Grafana run only when
requested and bind to 127.0.0.1:9090 and 127.0.0.1:3002. Prometheus scrapes
every 5 seconds and keeps two days of data under ignored .integration-data/.
# After the normal database/fixture setup, in separate local terminals:
# Start Prometheus first; when the env value is absent the harness creates an ignored per-run scrape secret.
$prometheus = 'C:\tools\prometheus\prometheus.exe'
.\scripts\start-observability.ps1 -PrometheusExe $prometheus -PrometheusOnly
.\scripts\start-compute-api.ps1 -Observability
.\scripts\start-compute-worker.ps1 -Observability
.\scripts\start-core-api.ps1 -Observability
# Supply locally installed binaries and a private password; never commit the password.
$password = Read-Host 'Local Grafana admin password' -AsSecureString
.\scripts\start-observability.ps1 -PrometheusExe 'C:\tools\prometheus\prometheus.exe' `
-GrafanaExe 'C:\tools\grafana\bin\grafana.exe' -GrafanaAdminPassword $password
Remove-Variable password
# Inspect http://127.0.0.1:9090/targets and open http://127.0.0.1:3002.
# Grafana's provisioned "RefInvest local flow and timing" dashboard is in RefInvest local.
.\scripts\stop-observability.ps1
.\scripts\stop-integration.ps1The three scrape jobs are core-api, compute-api, and compute-worker.
Compute API and Worker metrics are separate process-local registries on
127.0.0.1:9101 and 127.0.0.1:9102. The launch switches enable metrics only
for those local processes. Do not expose these ports, Prometheus, or Grafana on
another interface. The dashboard uses 5-minute windows; allow at least two
scrapes and run the normal API smoke to produce observations. A blank series
does not establish that collection works.
The core-api job uses a dedicated local scrape account and a password file
generated under ignored .integration-data/observability/. Core accepts it
only on the loopback metrics endpoint while dev-observability is active.
Unauthenticated requests still receive 401. Compute does not currently
expose production stage, cache, or active-job gauge metrics, so the dashboard
does not claim to show them. Worker attempt duration includes process/IPC and
heartbeat shutdown, not just engine execution.
After the Quick start database/fixture setup, start Core, Compute API, and
Compute Worker with the local scripts above. Keep their existing
refinvest_integration database and immutable integration-e2e-v1 fixture;
install local psql for a read-only DB status check. The command verifies
OpenAPI sync, creates a separate synthetic PRO member and StrategyVersion,
performs one warm-up run, then executes three same-condition processing repetitions and
three separate saturation/rejection repetitions with a drain and cooldown between them. It does not start or stop services, alter the
fixture, or delete existing rows.
.\scripts\run-load-test.ps1
# Short check with one processing repetition and one attempted saturation repetition.
.\scripts\run-load-test.ps1 -SteadyRate 1 -SteadyDuration 1 -BurstRate 10 -BurstDuration 1 `
-MaxSubmissions 10 -Concurrency 8 -Repetitions 1Defaults are processing 0.2 submissions/s for 30 seconds and saturation 2/s for 10
seconds, repeated three times, at most 30 submissions per repetition and 3 concurrent submission HTTP
requests. Terminal polling is limited to 120 seconds per accepted run at a
2-second interval; cooldown is 5 seconds. These bounds can be adjusted with
-SteadyRate, -BurstRate, -SteadyDuration, -BurstDuration,
-MaxSubmissions, -Concurrency, -TerminalTimeout, -PollInterval,
-Cooldown, and -Repetitions. The Python runner enforces upper limits. Polling to learn terminal
status is recorded separately from submission traffic; it creates no new job.
Each invocation writes result.json and summary.md under
.integration-data/performance/load-tests/<timestamp>/. The report includes
raw submission records, per-repetition request/accept/reject/failure/timeout/completion counts,
planned and actual submission rates, accepted-work and rejection-response latency distributions,
queue/worker metrics, fixture hashes, revisions, a read-only DB boundary comparison, clock-skew
estimates, missing observations, and available Prometheus measurements. Rejected requests are never
included in accepted-work terminal latency. The run fails when the configured saturation scenario
produces no rejection because that did not exercise the requested overload boundary.
Terminal latency includes every observed COMPLETED and FAILED transition;
timeouts and missing terminals are counted as censored observations, never as
successful latency samples. It is measured from submission start until the
client first sees terminal state, so it includes up to one poll interval of
observation delay. A nonzero exit means the run failed; inspect its
report, integration logs, and DB status. Core Prometheus values are N/A if
its target is DOWN; observability_status then remains partial.
This is runtime bottleneck exploration, not
the fixed-input benchmark or a performance gate. Stop only services you started
using stop-integration.ps1 -Service <name>; reports and submitted integration
rows remain for review.
The stage model distinguishes client submission, Core durable enqueue, Compute durable acceptance,
Worker queue wait, Worker attempt, Compute terminal commit, Core poll observation, Core terminal commit,
and client terminal observation. The report identifies the load generator, Core HTTP client, and Worker attempt
monotonic timers. Core scheduler lag and Compute queue wait use wall clocks, so they remain estimates rather than
exact cross-process durations.
Raw database and HTTP wall-clock timestamps only prove that boundaries occurred: the report estimates and
displays clock skew and never subtracts Core and Compute timestamps as an exact stage duration. If repeated
evidence cannot separate a candidate, the report remains indeterminate and names the missing per-run
instrumentation. A Compute execution candidate requires a new same-environment -Mode Official benchmark
before optimization; an official=false smoke artifact is never relabeled. A Core poll/persistence candidate
instead requires the same E2E load conditions before and after, because a Compute-only benchmark cannot
establish that effect.
Binance USDⓈ-M Linear Perpetual One-way reconstruction의 합성 Golden Dataset과 fixture catalog는
fixtures/trading-review/binance-usds-v1에 있다.
정적 schema, provenance, 보존식, checksum, 개인정보 namespace와 손상 거절 검증은 다음 명령으로 실행한다.
.\scripts\verify-trading-review-fixtures.ps1이 검증은 reconstruction을 구현하거나 실행하지 않으며, Trading Review API가 확정되기 전까지 endpoint 기반
E2E를 추측하지 않는다. Cross-service compatibility/E2E 전에는 기존 규칙대로 먼저
.\scripts\verify-contract-sync.ps1을 실행한다.
Copy .env.example to .env.local. Use an isolated database named
refinvest_integration; it must not point at a normal development or
production database. A locally running PostgreSQL instance is supported
directly; Docker Compose is optional.
# Optional Docker PostgreSQL
docker compose --env-file .env.local up -d postgres
docker compose --env-file .env.local ps
# Required before every cross-service test.
.\scripts\verify-contract-sync.ps1
# Compute-owned schema and deterministic fixture.
.\scripts\initialize-compute-schema.ps1
.\scripts\publish-compute-fixture.ps1The schema script uses local psql when available and falls back to Docker
Compose otherwise. It records only Compute migration filenames in the
integration-owned integration_schema_migrations table. Run it against a new
integration database. If a database predates this harness and has Compute
tables without those records, do not let the script infer migration history:
recreate the isolated database or explicitly inspect and record its history
before using it.
Start the backend processes in this order:
.\scripts\start-compute-api.ps1
.\scripts\start-compute-worker.ps1
.\scripts\start-core-api.ps1The start scripts record only the processes they launch in ignored,
profile-scoped .integration-data/processes/<registry-id>.json registries.
The default stack uses default; API smoke, browser acceptance, observability,
and each reliability run use separate registry IDs. A record contains PID and
start time (OS process identity) plus a secret-free configuration identity:
service role, execution profile, checkout path/revision, listen address,
database name/schema, snapshot profile/root, environment-file path, and the
service endpoints that affect topology. Dirty checkout state is diagnostic and
is not part of the reuse fingerprint.
A start script reuses a live recorded process only when PID/start time, role,
profile, repository revision, configuration fingerprint, listener ownership,
and health all match. A healthy endpoint is not sufficient. MISMATCH,
LEGACY, STALE, or UNOWNED_PORT is a hard failure and no process is stopped
or restarted automatically. Use the lifecycle helpers to inspect or explicitly
stop only the recorded profile:
.\scripts\status-integration.ps1
.\scripts\stop-integration.ps1
# Or stop only a recorded service.
.\scripts\stop-integration.ps1 -Service core-api
# Example for an acceptance profile.
.\scripts\status-integration.ps1 -Profile web-acceptance -RegistryId web-acceptance
.\scripts\stop-integration.ps1 -Profile web-acceptance -RegistryId web-acceptancestop-integration.ps1 verifies the recorded launcher PID and start time
before stopping its process tree. It never stops a listener or worker that was
not recorded by an integration start script. If status reports a schema
mismatch such as compute-api=ri_rel_* versus compute-worker=public, stop the
named service with the exact profile/registry command printed by status and
then start the intended stack. Do not kill the port owner directly.
status-integration.ps1 reports PID validation, listener ownership, health,
recorded/current revisions, database, schema, and identity state. MATCH is
reusable; MISMATCH is a live but incompatible managed service; LEGACY lacks
configuration identity; STALE no longer identifies the recorded OS process;
and UNOWNED_PORT means integration has no matching ownership record. Legacy
.integration-data/processes.json records are never assumed compatible; after
inspection, they can be stopped explicitly with
.\scripts\stop-integration.ps1 -Legacy.
Before submitting jobs, API smoke, Web acceptance, controlled load, and the reliability recovery path run a cross-service topology preflight. It requires Core/Compute to use the same integration database, Compute API/Worker to use the same schema and snapshot identity, Core to target the recorded Compute API (or its recorded fault proxy), Web to target the recorded Core endpoint, and Prometheus targets to match recorded observability endpoints.
start-core-api.ps1 also sets Gradle's user home to the ignored
.integration-data/gradle directory. This avoids a global Gradle cache and
keeps wrapper downloads and lock files writable in restricted local shells.
Wait for both health endpoints, then seed the test Member and execute the API smoke flow.
Invoke-RestMethod http://127.0.0.1:8000/health
Invoke-RestMethod http://127.0.0.1:8080/actuator/health
$token = .\scripts\seed-core-member.ps1 -Tier PRO
.\scripts\run-api-smoke.ps1 -AccessToken ($token | Select-Object -Last 1)
# Run this after the default API smoke: it publishes two additional immutable
# provenance-test snapshots to the same isolated database.
.\scripts\verify-binance-public-data-fixture.ps1The smoke uses normal HS256 Access JWT verification plus a CSRF cookie/header
pair. It requires Strategy creation, StrategyVersion creation, Compute
dispatch, and COMPLETED results with datasetSnapshotId=integration-e2e-v1.
It first preserves the trade-free same-market empty-state path, then runs a
deterministic Cross-Market scenario (BTCUSDT signal → TQQQ execution). The
second path requires realized trades, strategy and Buy & Hold equity curves,
signal/entry/exit timestamps, and the CROSS_MARKET relation. It asserts the
distinct BTCUSDT and TQQQ benchmark date sequences directly, so a Core or
Compute regression that forward-fills across their different calendars fails
the smoke. A third Cross-Market scenario uses a fixed 2% entry/exit commission
against the same immutable price fixture; it verifies a negative strategy MDD
and an equity point below its running peak, providing deterministic evidence
for Drawdown visualization without changing production or fixture data. It then
replays the identical request and requires the requested/actual period, fee
model, snapshot, engine version, and every result field except the per-run ID
to be identical. It also calls the version-specific latest-completed endpoint
before execution, after the initial run, and after the replay. This verifies
the no-result run: null response and that the replay becomes the displayed
latest completed result without relying on a paginated strategy-level history.
It additionally uses a separate integration member with a valid JWT to verify
that a foreign-owned StrategyVersion returns the contract's 404 response.
Finally, it starts a fixture-range-missing request and requires a terminal
FAILED result with CALENDAR_RESOLUTION_FAILED; the latest-completed endpoint
must still return the earlier successful run, while latest-terminal returns the
failed run. This prevents a refresh from hiding either the last usable chart or
the most recent failure.
verify-binance-public-data-fixture.ps1 is separate from the cross-service
smoke so it can exercise the real Compute PostgreSQL archive-revision and
DatasetSnapshot storage path without calling an external vendor. It serves two
fixed, checksum-valid monthly BTCUSDT ZIP fixtures for the same Binance URI:
one original and one revised payload. It confirms that the verified URI,
SHA-256, and retrieval timestamp are stored in each snapshot's provenance,
that a matching hash is reusable, and that a changed hash creates a second
immutable revision without changing the first snapshot. It uses the separate
integration_binance_fixture schema in the isolated integration database, so
its provenance-test snapshots cannot become the default snapshot selected by
the Core-to-Compute API smoke. Its artifacts are likewise isolated under the
ignored .integration-data/binance-fixture-snapshots directory.
The reliability harness uses the real Compute and Core HTTP endpoints, isolated
per-run PostgreSQL schemas inside refinvest_integration, and integration-owned
processes. It also starts a local fault proxy that forwards the first Compute
submission, observes its 202, and closes the Core connection before returning
the response.
.\scripts\run-durable-dispatch-reliability.ps1 `
-Concurrency 20 -Rounds 20 -DispatchRuns 10The command verifies contract sync before and after the run, executes the
Compute PostgreSQL integration tests, performs a fresh insert race in every
concurrency round, checks payload conflicts, submits Core runs while Compute is
down, then starts Compute and waits for terminal recovery. Machine-readable
results and partial failure artifacts are written under the ignored
.integration-data/reports/durable-dispatch-* directory. The command stops
only processes registered by this harness. See
docs/DURABLE_DISPATCH_RELIABILITY.md for assertions, safety boundaries, and
report semantics.
Use the same localhost host for Web and Core. With the integration default
SameSite=Lax, localhost and 127.0.0.1 are different sites, so a Web app
on localhost cannot reliably send Core cookies at 127.0.0.1.
# Starts Web with NEXT_PUBLIC_API_BASE_URL=http://localhost:8080 by default.
.\scripts\start-web.ps1The Web repository owns the Playwright test. The integration harness only
starts the real local stack, seeds an isolated PRO member, and supplies its
short-lived integration token to the browser process without logging it. The
test creates its own BTCUSDT signal → TQQQ strategy version and a completed
run using the immutable integration-e2e-v1 fixture with commission: 0.02.
It opens the version URL directly, verifies the visible Equity Curve, Drawdown,
Strategy/Buy & Hold comparison, timeline, and negative MDD, then verifies the
same result after reload. It then creates a deterministic calendar-resolution
failure for the same version and verifies direct entry and reload retain the
earlier successful charts while showing a user-safe failure message and its
structured failure code.
After completing the Quick start setup, install the Web test dependency and Chromium once, then run the combined acceptance command:
pnpm --dir ..\web\web install
pnpm --dir ..\web\web exec playwright install chromium
.\scripts\run-web-acceptance-ci.ps1This command first executes verify-contract-sync and the Core→Compute API
smoke. It uses response polling and visible-page conditions instead of fixed
browser sleeps. Run stop-integration.ps1 afterwards when using the local
harness.
run-auth-smoke.ps1 checks actual Core HTTP/Cookie behavior after a real OAuth
login has issued a refresh cookie. Supply a fresh token for each scenario; a
refresh-replay check intentionally revokes that token family. The script never
prints token values.
.\scripts\run-auth-smoke.ps1 -Scenario RefreshRotation -RefreshToken '<fresh token>'
.\scripts\run-auth-smoke.ps1 -Scenario Logout -RefreshToken '<different fresh token>'For manual provider acceptance, set REFINVEST_OAUTH_ENABLED=true and all six
provider credentials in .env.local, then start Core. Register each provider
callback as http://localhost:8080/login/oauth2/code/{provider}. Verify that
Kakao, Naver, and Google each return a provider authorization 302, that a
normal returnTo (for example /strategies?tab=active) survives the callback,
and that external URLs, //host/path, and backslash paths are rejected or
normalized to /. Do not add fake providers, callback routes, or credentials
to automate this step.
After credentials are configured, the authorization-start portion is repeatable:
.\scripts\run-oauth-start-smoke.ps1It checks each provider authorization redirect and that the redirect includes
OAuth state. It also expects unsafe returnTo values to receive the contract's
400 response. Callback completion remains a browser/manual check because it
requires a real provider account and registered callback.
For each configured provider, use this browser acceptance checklist without copying Cookie or token values from browser tools:
- Open
http://localhost:3001/login?returnTo=%2Fstrategies%3Ftab%3Dactiveand complete the provider login. - Confirm the callback returns to
/strategies?tab=active. - Use Account -> Sign out and confirm the Web app moves to
/login. - Reopen
http://localhost:3001/strategies?tab=active; it must move to/login?returnTo=%2Fstrategies%3Ftab%3Dactiveafter the protected request cannot refresh the session.
The final step verifies the actual browser path: Core clears the OAuth HTTP
session as well as its Access/Refresh cookies and Refresh Token family; Web
then handles the protected-request 401, failed refresh 401, and preserved
returnTo. A refresh 403 caused by a missing or invalid CSRF header remains
a security error and must not be treated as a successful refresh or retried.
Browser/Web (localhost:3001)
|
v
Core API (localhost:8080) ---- internal API key ----> Compute (localhost:8000)
| |
+---------------- shared PostgreSQL ----------------+
Core's COMPUTE_API_KEY is derived from the single
REFINVEST_INTERNAL_API_KEY environment value. Do not add a second key with a
different value. Compute receives that same value as its internal API key.
Generate machine-readable evidence for the exact sibling revisions and normalized OpenAPI hashes used by a local run:
.\scripts\write-compatibility-evidence.ps1 `
-VerificationName local-api-smoke `
-VerificationOutcome success `
-RequiredRepositories context,coreApi,computeApi,web,integrationThe ignored output is written to
.integration-data/evidence/compatibility.json. Contract-sync and API-smoke CI
runs generate and upload the same evidence even when their verification step
fails. A detached CI checkout records a null branch while preserving the exact
commit SHA. Dirty local worktrees are reported as dirty; secret values and
remote URLs are never recorded.
Successful evidence generation also requires the selected repositories to be
present and the Context/Core/Compute contract hashes to match. When Web is a
required repository, its Core API and $ref-only Compute API snapshot hashes
must also match Context. Missing sibling checkouts are still recorded when a
failed or skipped CI run generates evidence.
compatibility.example.yaml remains available only for an explicitly selected
local combination that has not been exercised by the automated harness. Any
compatibility evidence records observations and never supersedes context.
Context snapshot propagation rules, per-repository revision semantics, and the bot PR rollout are documented in Contract snapshot propagation.
.github/workflows/contract-sync.yml runs the mandatory OpenAPI and Core
document snapshot checks for integration pull requests, pushes to main, daily
scheduled runs, and manual dispatches. It checks out context, core-api,
compute-api, and web beside this repository using the same sibling paths as
the local harness. The OpenAPI gate verifies both Web snapshots as well as Core
and Compute snapshots. The scheduled run detects drift introduced by changes
merged directly in any sibling repository.
Before enabling the workflow for private sibling repositories, install a GitHub
App on exactly integration, context, core-api, compute-api, and web
with Contents: Read permission. Configure the integration repository with:
- Actions variable
REFINVEST_CI_APP_CLIENT_ID - Actions secret
REFINVEST_CI_APP_PRIVATE_KEY
The workflow creates a short-lived token scoped to the four sibling
repositories. It does not receive OAuth credentials, database passwords, or
internal API keys. Pull requests from forks are skipped because repository
secrets are intentionally unavailable to them. Compatibility evidence records
the combined OpenAPI and Core document verification as snapshot-sync.
.github/workflows/api-smoke.yml uses an ephemeral PostgreSQL service and the
same deterministic Compute fixture as the local harness. It installs no OAuth
credentials and runs the actual Core-to-Compute path: strategy creation,
dispatch, worker execution, and terminal-result persistence. The workflow
first repeats the mandatory OpenAPI snapshot check, then uploads Core, Compute,
and worker logs as api-smoke-logs on both success and failure.
The workflow uses the same read-only GitHub App configuration as contract sync. It is intentionally skipped for fork pull requests because the App private key is unavailable there.
Web browser acceptance now runs alongside the API smoke. Web continues to call Core only, never Compute directly.