okstra 0.217.0 → 0.218.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.217.0",
3
+ "version": "0.218.0",
4
4
  "description": "Host-aware multi-provider cross-verification orchestrator runtime and agent skills.",
5
5
  "license": "MIT",
6
6
  "author": "devonshin",
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.217.0",
3
- "builtAt": "2026-10-16T10:55:22.511Z",
2
+ "package": "0.218.0",
3
+ "builtAt": "2026-10-16T12:08:35.107Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -327,6 +327,30 @@ For every source file this change touches, follow the paths the change creates o
327
327
 
328
328
  **A finding names the input or state that produces the wrong result.** *"If the archive yields no entries, line 42 reports success and stores an empty result"* is a finding. Code that works as written is `clean`, however you would have written it differently: alternative structures, guards for states no caller can reach, extra tests for covered paths, and "consider extracting / renaming / memoizing" are improvements, not findings. A real failure that is small and cheap to fix is still a finding.
329
329
 
330
+ ## Untrusted input and security
331
+
332
+ Input from outside the process — request bodies, query strings, headers, uploaded files, queue messages, webhook payloads — is controlled by whoever sends it, authenticated or not. These checks apply to every changed file that reads such input or acts on its behalf. **A finding names the input and what it does:** *"a body of 100k fonts with distinct keys runs the O(n²) uniqueness check before the size limit rejects it"* is a finding; "could be more secure" is not.
333
+
334
+ ### Bound the work before the expensive check
335
+
336
+ A size or count limit protects nothing if a costlier check still runs on the oversized input. Many validators run every rule on a field rather than stopping at the first failure — class-validator under a `ValidationPipe` without `stopAtFirstError`, for one — so `@ArrayMaxSize(50)` failing does not stop `@ArrayUnique`, whose comparison is O(n²). Any work that grows faster than the input — pairwise uniqueness, nested scans, a backtracking regex, recursion or expansion driven by the input — runs only after a bound has rejected oversized input, inside the same check (`if (items.length > MAX) return false;` then a `Set`), or the framework is configured to stop at the first failure. The body-size limit is not that bound: it caps bytes, not the element count the costly step loops over. Pin it with a test that sends an input well past the limit and asserts the rejection returns quickly (see "Resource bounds need their own regression test").
337
+
338
+ ### Input never builds code
339
+
340
+ Input that reaches SQL, a shell command, a file path, a URL fetched server-side, a template, or a regular expression goes through a parameter, an allowlist, or an escaping API for that sink — never string concatenation or interpolation. A path from input is resolved and checked to stay under its root; a server-side fetch target is checked against an allowlist of hosts.
341
+
342
+ ### Every new entry point checks who is calling and what they own
343
+
344
+ A new route, handler, resolver, or job that reads or writes per-user data carries the project's authentication guard, and a lookup by an id from the input compares the caller's identity with the resource's owner before returning or changing it. An id the caller can choose is not proof of ownership.
345
+
346
+ ### Secrets and personal data stay out of code, logs, and responses
347
+
348
+ No secret, token, or credential in source or fixtures meant for production. Logs, error messages, and response bodies carry no secret, full token, password, or personal data — including inside an echoed request body or an upstream error passed through verbatim.
349
+
350
+ ### No execution of input
351
+
352
+ No `eval`, `Function(...)`, dynamic `import()` of an input-chosen path, or deserializer that can instantiate arbitrary types (`pickle`, Java native serialization, YAML loaders without a safe mode) applied to input.
353
+
330
354
  ## No magic numbers
331
355
 
332
356
  Replace hardcoded values with named constants.
@@ -50,6 +50,7 @@ If no Stage 1 language rule matches (an unlisted language), stop and ask the use
50
50
  - [ ] Language reference read for this turn.
51
51
  - [ ] `clean-code.md` principles applied: DRY, KISS, SOLID, YAGNI, meaningful naming (truthful + standalone + one identifier one meaning per file), single-purpose functions, plain-English summary test, 50-line cap, no magic numbers, shallow nesting, comments-explain-why.
52
52
  - [ ] **Mutation and state boundaries** (`clean-code.md`): decide on the direct identifier, not a status/flag proxy; capture before-state in one snapshot ahead of the mutating boundary; update only this work's owned fields on an existing row; re-read state before calling a zero-affected-rows write success or failure; put priority-between-inputs in a named domain function; keep error messages to what was observed.
53
+ - [ ] **Untrusted input and security** (`clean-code.md`), for every file that reads input from outside the process: a size/count bound rejects oversized input before any check or step that grows faster than the input; input reaches SQL, shell, paths, server-side fetches, templates and regexes only through parameters, allowlists or escaping; a new entry point carries the auth guard and an ownership check on input-chosen ids; no secrets or personal data in code, logs or responses; no eval or unsafe deserialization of input.
53
54
  - [ ] Tests planned: which test(s) cover this change. New behaviour without a test is **incomplete** unless the user has explicitly opted out for this change.
54
55
  - [ ] **Existing tests surveyed (run this before planning any new test):** for every surface this change touches, `grep -rln` the candidate test files, list their test names, then READ the bodies of the ones that could overlap. A list of names does not tell you what a test asserts, and behavioural overlap does not collide on names — treat a clean name search as unfinished, not as a clear result. Record per surface: `extend <path:line>` / `new (nothing covers <behaviour>)` / `retire <path:line> (behaviour moved to <where>)`. A new test or test file with no survey line is unfinished work. The survey may change the design — moving the seam so existing tests keep passing beats adding parallel ones. Carry out a `retire` verdict in the same commit, on behavioural grounds only: the behaviour it asserted is gone, or a new test asserts it more strongly through the same seam. "Looks similar" / "touches the same file" is not grounds; without grounds the test stays, because a silent coverage loss costs more than one extra test.
55
56
  - [ ] **Testing discipline:** the test does not stub/spy methods on the SUT itself (collaborators are fine), and assertions are on outcomes (return values, state, events, boundary calls) — not on which internal helper was called. Each branch this change adds (`catch`, guard, early return, `else`) has a test that fails when the branch body is deleted; assertions land on the last write to a record, not an intermediate one; each test title names its unit and the single condition it isolates; no effect is claimed under its own mock; shared fixtures keep their ordinary defaults; the scenarios' setup values actually differ; every new test helper/mock is used by a test in this same change; no positional mock-argument access (`rg 'mock\.calls'`).
@@ -37,6 +37,7 @@ Do not scan holistically and stop when it "looks fine". Work the matrix exhausti
37
37
  - test file (`*.spec.*` / `*.test.*` / `test_*.py` / `*_test.go` …) → `clean-code.md` "Testing discipline": no self-mocking of the SUT, behavioral (outcome) assertions not interaction-only, no tautological delegation assertion, no effect claimed under its own mock, shared-fixture defaults left on the ordinary path, setup values that actually separate the scenarios, every new test helper/mock used by a test in this same diff, no positional mock-argument access (`rg 'mock\.calls'`), every branch this diff adds covered by a test that fails when the branch body is deleted, assertions on the last write to a record rather than an intermediate one, and test titles naming their unit plus the single condition each case isolates.
38
38
  - port / adapter / domain file, when the hexagonal overlay is loaded → `architectures/hexagonal.md`: no business logic in a port body, adapter methods are I/O only (no post-fetch filtering on domain state, no `findValid*`/`findActive*` names hiding a rule), domain objects declared under the domain boundary, no changed domain file importing an ORM / framework / adapter / service (read the import list — mechanical), and a service dependency you add or modify goes through a port rather than a concrete adapter (advisory only while the project has not declared `architecture.style = hexagonal` in `.okstra/project.json` — record it with the port sketch; blocking under that declaration, so fix it in place before the commit rather than record-and-pass, exactly as `_implementation-verifier.md` re-grades this same diff. Either way, the codebase already injecting concrete classes is the debt this pays down, not a reason to skip it).
39
39
  - a service file, when the hexagonal overlay is loaded → `architectures/hexagonal.md` Rule H6: a decision this diff puts in a service — an `if`/`else` chain over domain fields, a business formula, a private method named for a domain concept — belongs in a domain function. Orchestration control flow, DTO mapping and calling a domain predicate are not violations.
40
+ - a file that reads input from outside the process (request body, query, header, upload, message, webhook) or acts on its behalf → `clean-code.md` "Untrusted input and security": a size/count bound rejects oversized input before any check that grows faster than the input (a validator that runs every rule, such as class-validator without `stopAtFirstError`, does not stop at a failed `@ArrayMaxSize`), input reaches SQL / shell / paths / server-side fetches / templates / regexes only through parameters, allowlists or escaping, a new entry point carries the auth guard and compares the caller with the owner of an input-chosen id, no secret or personal data reaches code, logs or responses, and nothing evaluates or unsafely deserializes input.
40
41
  - every changed source file → `clean-code.md` "Trace what this change can do wrong": walk the error, partial, concurrent and selection paths this diff creates to their end and fix wherever a wrong result comes out. Name the input that produces it — if you cannot name one, there is nothing to fix here and you move on rather than restructuring code that works.
41
42
  A file can hold several roles — apply every rule set that fits it.
42
43
  3. **Decide clean-or-finding for each cell.** Read the full file when a rule needs context (never judge a port/adapter/domain or a naming rule from the hunk alone).
@@ -193,6 +193,7 @@ Re-running commands proves the diff *builds and passes*; it does NOT prove the d
193
193
  - **Load the project's review rule packs.** Run the project-context projection above and union its `Project Review Rule Packs` entries with exact `SKILL.md` paths cited by the task brief's `Source Material` / `Reporter Confirmations`. Read only those files and the `references/*.md` files they directly name. Do not search parent directories or host skill catalogs. Apply the rules as an overlay on this static review, but do NOT dispatch extra reviewer agents unless the task explicitly configured them. Record `project-review-rules: <paths read>`, `project-review-rules: declared <path> unreadable`, or `project-review-rules: none declared or cited` in the worker result — an unreadable declared pack is a recorded gap, not a skip.
194
194
  - **Declared architecture style promotes the placement overlay from advisory to binding.** Take `Architecture style` from that projection and record `architecture-style: <hexagonal|layered|none>` in the worker result next to the `coding-conventions:` line. A declared `hexagonal` counts the overlay as loaded even when none of the router's Stage 3 layout signals matched, so the **Hexagonal** blocking check below applies in full, and the concrete-adapter injection listed under Advisory findings is promoted to a blocking finding → verdict `FAIL`, not a `should-fix`. A declared `layered` has no pack resource; its binding invariant is direction — an upper layer may import a lower one, never the reverse — so a changed file whose import list reaches back up a layer, or around a layer boundary, is a blocking placement violation cited `path:line` from that import list. The `layered` half is worker judgement: no machine check reads layer names, so a missed reverse dependency is a missed finding, not a validator failure. A `none` or absent projected style leaves Stage 3 detection-driven and the placement items advisory. **Enforced:** `scripts/okstra_project/resolver.py` `resolve_architecture` reads the same stored field for the planning-side rule in `scripts/okstra_ctl/phases/implementation_planning/plan_body.py` `_validate_variation_point_analysis`, and `_validate_verifier_fail_blocks_verdict` keeps the resulting `FAIL` from being dropped during synthesis.
195
195
  - **Blocking checks (any hit → verdict `FAIL`, cited `path:line` + rule name, recommended fix recorded — the verifier does NOT apply it):**
196
+ - **Security (every changed file that reads input from outside the process or acts on its behalf — `clean-code.md` "Untrusted input and security"):** work that grows faster than the input — pairwise uniqueness, nested scans, a backtracking regex, input-driven recursion — reachable before a size/count bound has rejected oversized input (read the validation framework's configuration: class-validator without `stopAtFirstError` still runs `@ArrayUnique` after `@ArrayMaxSize` fails, and a byte-size body limit does not bound element count); input concatenated into SQL, a shell command, a file path, a server-side fetch target, a template or a regex; a new route / handler / resolver without the project's auth guard, or a lookup by an input-chosen id that never compares the caller with the owner; a secret, full token, password or personal data written to source, logs, an error message or a response; `eval`, dynamic code, or unsafe deserialization applied to input. Cite the input that triggers it and its effect — for unbounded work, the input size and the measured or derived cost.
196
197
  - **New duplication / DRY:** two or more newly added or meaningfully modified blocks implement the same helper stack, transform, or domain rule. Literal copy-paste is always blocking; semantically equivalent transforms across services are blocking unless the approved plan explicitly justified keeping them separate. Recommend the shared module location.
197
198
  - **Self-mocking:** a test for `Foo` stubs/spies a method on the `Foo` instance under test (`jest.spyOn(sut, ...)`, `spyOn(FooService.prototype, ...)` in `foo.*.spec.*`, `vi.mocked(sut)` + stub). Mocking injected collaborators is fine.
198
199
  - **Interaction-only assertion:** a test whose only/primary assertion is `toHaveBeenCalled*` / `toHaveBeenCalledTimes` on an internal helper or a non-side-effecting collaborator, with no assertion on the returned value / resulting state / persisted row / emitted event.