diff --git a/audit/AUDIT.md b/audit/AUDIT.md new file mode 100644 index 000000000..7b770147f --- /dev/null +++ b/audit/AUDIT.md @@ -0,0 +1,179 @@ +# AUDIT — Rayforce (C engine, Rayfall runtime, tests, and documentation) + +Operator language: English. + +## 0. Scope and batching rules + +The audit covers authored, tracked project material: `include/`, `src/`, `test/`, +`fuzz/`, `examples/`, `bench/`, `docs/`, `website/`, `overrides/`, `scripts/`, +`packaging/`, `.github/`, and the root build/governance files. Each tracked file +is an addressable unit. A heading or enclosing-symbol range is an addressable +sub-unit when a Markdown or C file is too large for one attentive pass. + +Queue globs are resolved with `git ls-files` at the beginning of a pass; the +resolved file list and any symbol/section split must be recorded in the pass +report. Brace sets such as `{arena,cow}` are compact notation: expand every +listed member into its own pathspec. Related implementation/header pairs and +small tests may be batched. +Large units such as `src/ops/query.c`, `src/ops/group.c`, `test/test_exec.c`, and +`test/test_lang.c` must be split by enclosing symbols if the charter cannot be +completed attentively. Any unfinished range becomes a new queued pass under +XCHECK.md §4 rule 4. + +Excluded: `.git/`, `.worktrees/`, `.agents/`, `.codex/`, `audit/` itself, +untracked/generated objects and binaries (`*.o`, `*.d`, `*.a`, `rayforce*`), +coverage/fuzz corpora, release output, caches, and vendored font/image binary +content. Generated/static site output is checked only for contracts that the +repository intentionally maintains (redirects, navigation, and brand shell), +not for minified or third-party asset internals. + +Inventory baseline (2026-07-22): 169 tracked C/header files under `src/`, one +770-line public header, 72 C tests plus four test headers, 432 Rayfall tests, +98 authored files under `docs/docs/`, eight fuzz source/policy files, and ten +GitHub policy/workflow files. Counts are planning estimates, not pass evidence. + +## 1. Norms catalog + +| id | norm source | scope | +|---|---|---| +| N01 | `include/rayforce.h` | Public types, ownership/lifetime, error, vector, table, progress, and API contracts. | +| N02 | `src/**/*.h`, especially `src/ops/ops.h`, `src/core/runtime.h`, `src/mem/*.h`, and `src/store/*.h` | Internal module interfaces and invariants; use only the header relevant to the audited implementation. | +| N03 | `docs/docs/c-api/*.md`, `docs/docs/architecture/*.md`, `docs/docs/language/*.md`, `docs/docs/reference/all-functions.md`, `docs/docs/storage/*.md`, and `docs/docs/guides/{errors,memory}.md` | Published behavior, architecture, language, storage/wire, error/null, and lifecycle contracts. | +| N04 | `README.md` | Product capabilities, supported workflows, project structure, examples, and performance/architecture promises. | +| N05 | `test/*.c`, `test/rfl/**/*.rfl`, and `test/test.h` | Executable behavior and regression contracts. A test is a norm only for behavior it asserts explicitly. | +| N06 | `CONTRIBUTING.md` and `Makefile` | C17, zero-dependency, warnings-as-errors, sanitizer, TSan, fuzz, test-discovery, and build-flavor policy. | +| N07 | `.clang-tidy` and `.tsan-suppressions` | Correctness-only static-analysis policy and the requirement that race suppressions be justified. | +| N08 | `fuzz/README.md`, fuzz drivers, seed policy, and `Makefile` fuzz targets | Untrusted-input surfaces, sandbox restrictions, fuzz coverage, and crash-regression procedure. | +| N09 | `RELEASE.md`, `packaging/*`, `.github/workflows/release.yml`, and `.github/release-notes.sh` | Version, release, artifact, packaging, and supported-platform contracts. | +| N10 | `mkdocs.yml`, `docs/index.md`, and the tracked redirect/static shell files | Documentation navigation, published paths, exclusions, redirects, and site structure. | +| N11 | `.github/workflows/{ci,nightly,version-guard,retarget-prs,pages,rayforce-audit}.yml` | Automated branch, CI, stability, documentation, and audit gates. | +| N12 | ISO C17 and the platform APIs selected by `Makefile`/`src/core/platform.h` | Language-level undefined behavior, integer/pointer rules, atomics, and declared POSIX/Windows portability. Use an exact clause or platform API contract in findings. | +| N13 | Internal consistency | For contradictions not governed elsewhere, quote both conflicting passages as the norm and evidence. | + +## 2. Dimensions + +| key | what it catches | norm source | +|---|---|---| +| memory-lifetime | leaks, double release/free, use-after-free, invalid COW mutation, arena/heap ownership errors, overflow and out-of-bounds access | N01, relevant N02 header, N03 memory docs, N06 sanitizer policy, N12 | +| concurrency-portability | data races, broken atomics, unsafe cross-thread ownership, backend divergence, unsupported platform assumptions | N02, N03 architecture docs, N06, N07, N11, N12 | +| language-semantics | parser/compiler/evaluator/builtin behavior inconsistent with syntax, null/error rules, function reference, or executable tests | N03, N05, N13 | +| query-correctness | optimizer rewrites or execution paths that change results, mishandle nulls/types, or violate DAG/parallel execution contracts | N02, N03 pipeline/DAG docs, N05, N13 | +| storage-integrity | corrupt or incompatible serialization, journal/AOF replay errors, unsafe file handling, symbol-domain loss, persistence/IPC contract drift | N01, N02, N03 storage/IPC docs, N05, N08 | +| api-doc-consistency | declarations, ownership, errors, examples, and registered builtins that disagree across public headers, docs, implementation, or tests | N01, N02, N03, N04, N05, N13 | +| resilience-security | untrusted parser/decoder/input failures, sandbox escapes, restricted-mode gaps, crash/DoS paths, missing fuzz regression coverage | N06, N08, N11, N12 | +| build-release | debug/release/hardened semantic drift, missing source coverage, non-reproducible versioning, broken CI/release/packaging/platform promises | N04, N06, N09, N11, N12 | +| test-adequacy | normative behavior with no meaningful assertion, tests that cannot detect the promised failure, or suites omitted from discovery/gates | N03, N05, N06, N08, N11 | +| docs-structure | broken navigation/redirects, orphaned or duplicated reference material, stale project structure, or internal contradictions | N04, N10, N13 | +| performance-contract | optimizations that violate result semantics or documented resource/complexity claims; benchmark scenarios that do not measure their stated path | N03 architecture docs, N04, `bench/**`, N13 | + +Stylistic preferences are not an audit dimension: `.clang-tidy` explicitly +turns style churn off. A style-only observation has no project norm and is at +most `info` under XCHECK.md §6. + +## 3. Unit map + +| unit family | tracked units / size estimate | audit use | +|---|---:|---| +| Root contracts | `README.md`, `CONTRIBUTING.md`, `RELEASE.md`, `Makefile`, `.clang-tidy`, `.tsan-suppressions`, `mkdocs.yml` (7 files, ~1.1k lines plus 407-line Makefile) | Product, build, analysis, release, and documentation norms. | +| Public API | `include/rayforce.h` (1 file, 770 lines) | Public ABI/API, ownership, errors, and types. | +| Application | `src/app/*` (5 files, ~4.3k lines) | CLI lifecycle, REPL, terminal, formatting. | +| Core/platform | `src/core/*` (34 files, ~7.4k lines) | Runtime, types, pool, progress, IPC/socket, platform backends, diagnostics. | +| I/O | `src/io/*` (2 files, ~3.4k lines) | CSV parsing/writing and parallel I/O. | +| Language | `src/lang/*` (15 files, ~9.5k lines) | Parser, compiler, evaluator/VM, environments, special commands. | +| Memory | `src/mem/*` (8 files, ~3.6k lines) | Arena, buddy heap, system allocation, COW/refcounts. | +| Operations | `src/ops/*` (67 files, ~95.3k lines) | DAGs, optimizer, execution, relational/graph/vector operations and builtins. | +| Storage | `src/store/*` (20 files, ~8.0k lines) | AOF/journal, serde, column/partition stores, CSR, HNSW, metadata. | +| Tables/symbols | `src/table/*` (8 files, ~3.8k lines) | Tables, dictionaries, symbol domains/interning. | +| Values/vectors | `src/vec/*` (10 files, ~2.6k lines) | Atoms, vectors, lists, strings, selections, embedding contract. | +| C test harness | `test/*.c`, `test/*.h` (76 files, ~125k lines) | Executable contracts and adequacy checks; the largest files require symbol splits. | +| Rayfall tests | `test/rfl/**/*.rfl` (432 small files, ~72.9k lines in 37 subject directories) | Language/output regression contracts; batch by subject directory. | +| Fuzzing | `fuzz/*.c`, `fuzz/common.h`, `fuzz/README.md`, committed dictionaries/seeds (43 tracked units; drivers/policy ~425 lines) | Parser, decoder, evaluator, CSV, and journal adversarial coverage. | +| Examples | `examples/*.c`, `examples/rfl/**/*.rfl` (25 files, ~1.3k lines) | User-facing API and language behavior. | +| Benchmarks | `bench/**` (14 tracked files, ~4.6k lines) | Performance claims and benchmark validity. | +| Documentation | `docs/docs/**/*.md`, docs root content/config support, `docs/llms.txt` (109 authored text/code units, ~19.1k lines; binary assets separate) | Published contracts, structure, links, and reference completeness. | +| Site shell | `overrides/**`, authored `website/*.{html,css,js}`, redirect stubs (about 9 principal authored units, ~2.7k lines) | Navigation, redirects, and brand shell consistency. | +| Automation/release | `.github/**` (10 files, ~883 lines), `scripts/**` (5 files, ~444 lines), `packaging/**` (2 files), root release scripts | CI/nightly/release/security gates and packaging. | + +## 4. Pass queue + +All passes stop when their resolved scope is exhausted, 15 findings are filed, +or context is exhausted. In the latter two cases the Auditor records explicit +coverage and queues the unresolved files/symbol ranges as a new pass. + +- [x] P-01 — api-doc-consistency × public lifecycle: compare `include/rayforce.h`, `docs/docs/c-api/core.md`, and `test/test_public_api.c` for declarations, ownership, error, and lifecycle contracts. +- [ ] P-02 — memory-lifetime × arena/COW: audit `src/mem/{arena,cow}.{c,h}` in implementation/header pairs; split into two passes if needed. +- [ ] P-03 — memory-lifetime × heap/system allocator: audit `src/mem/{heap,sys}.{c,h}` against N01/N02/N03/N06/N12. +- [ ] P-04 — memory-lifetime × atoms/vectors: audit `src/vec/{atom,vec}.{c,h}` against public type, allocation, null, and refcount contracts. +- [ ] P-05 — memory-lifetime × list/string/selection: audit `src/vec/{list,str,sel}.{c,h}` plus `src/vec/embedding.h`; split by module. +- [ ] P-06 — memory-lifetime × tables/symbol domains: audit `src/table/{dict,domain,sym,table}.{c,h}`; split by implementation/header pair. +- [ ] P-07 — memory-lifetime × runtime/types: audit `src/core/{runtime,types}.{c,h}` against N01 and error/null/lifetime docs. +- [ ] P-08 — query-correctness × blocks/morsels: audit `src/core/{block,morsel}.{c,h}` against pipeline and execution contracts. +- [ ] P-09 — concurrency-portability × pool/progress/platform: audit `src/core/{pool,progress,platform}.{c,h}`; split by module and record backend assumptions. +- [ ] P-10 — concurrency-portability × event backends: compare `src/core/{poll,epoll,kqueue,iocp}.c` and relevant declarations for semantic parity. +- [ ] P-11 — resilience-security × IPC/socket: audit `src/core/{ipc,sock}.{c,h}` and the applicable IPC tests for framing, authentication/restriction, cleanup, and platform error paths. +- [ ] P-12 — resilience-security × diagnostics/timing: audit `src/core/{crash,qlog,qmeasure,timer}.c` and their headers (`qstats.h`, `profile.h`, etc.) for failure-path safety and hardened-build promises. +- [ ] P-13 — api-doc-consistency × CLI/REPL: audit `src/app/*` against README quick-start/REPL behavior and focused `test/test_{repl,term}.c`; split by module. +- [ ] P-14 — language-semantics × parser/numeric parser: audit `src/lang/{parse,compile}.c` only for parsing/compilation boundaries plus `src/core/numparse.{c,h}`; split large symbol ranges. +- [ ] P-15 — language-semantics × evaluator/VM: audit `src/lang/eval.{c,h}`, `src/lang/cal.h`, and `src/lang/internal.h` against syntax/error/null contracts; split `eval.c` by symbols. +- [ ] P-16 — language-semantics × environment/format/system commands: audit `src/lang/{env,format,nfo,syscmd}.{c,h}` against namespace, rendering, and restricted-mode norms. +- [ ] P-17 — storage-integrity × CSV: audit `src/io/csv.{c,h}`, `test/test_csv.c`, and `docs/docs/storage/index.md` CSV sections; split `csv.c` by symbols. +- [ ] P-18 — storage-integrity × serialization/file metadata: audit `src/store/{serde,fileio,meta}.{c,h}` against wire/storage docs and relevant tests. +- [ ] P-19 — storage-integrity × column/partition/splay: audit `src/store/{col,part,splay}.{c,h}` against storage layout, pruning, and round-trip contracts; split by module. +- [ ] P-20 — storage-integrity × AOF/journal: audit `src/store/{aof,journal}.{c,h}`, focused tests, and documented replay/integrity contracts. +- [ ] P-21 — storage-integrity × CSR/HNSW: audit `src/store/{csr,hnsw}.{c,h}` against graph/vector persistence contracts and focused tests; split by subsystem. +- [ ] P-22 — query-correctness × aggregation engine: audit `src/ops/agg.c`, `agg_engine.{c,h}`, `agg_stream.c`, `agg_acc.h`, and `agg_registry.h`; split by engine phase. +- [ ] P-23 — language-semantics × scalar operations: audit `src/ops/{arith,cmp,temporal,string,strop}.c` with corresponding reference sections and tests; split by operation family. +- [ ] P-24 — resilience-security × builtin/system surface: audit `src/ops/{builtins,system,glob}.c` for registration, error propagation, restricted mode, and fuzz sandbox contracts. +- [ ] P-25 — language-semantics × collections/vectors: audit `src/ops/{collection,fvec}.c` and `fvec.h` against collection/function reference and tests; split large files by symbols. +- [ ] P-26 — query-correctness × datalog/LFTJ: audit `src/ops/{datalog,lftj}.{c,h}` against published APIs and focused tests; split by subsystem. +- [ ] P-27 — storage-integrity × operation journal/dump: audit `src/ops/{journal,dump}.c` and `journal.h` against serialization/replay norms. +- [ ] P-28 — query-correctness × embedding/rerank: audit `src/ops/{embedding,rerank}.c` against vector-search docs and executable tests. +- [ ] P-29 — query-correctness × executor/expression VM: audit `src/ops/exec.{c,h}` and `src/ops/expr.c`; each large file gets its own symbol-range subpass. +- [ ] P-30 — query-correctness × filtering/fused paths: audit `src/ops/{filter,fused_pred,fused_topk}.c` and `fused_{group,pred,topk}.h`; split by path. +- [ ] P-31 — query-correctness × graph/traversal: audit `src/ops/{graph,graph_builtin,traverse}.c` and `graph.h` against graph docs/tests; split by algorithm families. +- [ ] P-32 — query-correctness × grouping/hash/HLL: audit `src/ops/group.c`, `hash.h`, and `hll.{c,h}`; `group.c` must be split by enclosing symbol ranges. +- [ ] P-33 — query-correctness × index/link operations: audit `src/ops/{idxop,linkop}.{c,h}` against index/link docs and tests. +- [ ] P-34 — query-correctness × joins: audit `src/ops/join.c` against join contracts and focused C/Rayfall tests; split by join strategy. +- [ ] P-35 — query-correctness × optimizer/planner: audit `src/ops/{opt,idiom,plan}.{c,h}` against the documented pass order and focused optimizer tests. +- [ ] P-36 — query-correctness × query engine: audit `src/ops/query.c` against query docs/tests; split into explicit select/update/partition/execution symbol ranges. +- [ ] P-37 — query-correctness × pipe/row selection: audit `src/ops/{pipe,rowsel}.{c,h}` against selection/null and pipeline contracts. +- [ ] P-38 — query-correctness × pivot/table operations: audit `src/ops/{pivot,tblop}.c` against table operation docs/tests. +- [ ] P-39 — query-correctness × sort/window: audit `src/ops/{sort,window}.c` against null/order/window contracts; split `sort.c` by symbols. +- [ ] P-40 — api-doc-consistency × DAG registry: compare `src/ops/{ops,internal}.h`, opcode/registration tables, `docs/docs/c-api/dag.md`, and `test/test_agg_registry.c`; split declaration families. +- [ ] P-41 — api-doc-consistency × complete builtin surface: compare the registered runtime surface with `docs/docs/reference/all-functions.md` and `docs/docs/language/functions.md`; record exact missing/extra/signature cases. +- [ ] P-42 — api-doc-consistency × language syntax: compare `docs/docs/language/{syntax,control-flow,math,string,repl}.md` with parser/evaluator behavior and focused Rayfall tests; split by document. +- [ ] P-43 — api-doc-consistency × query/architecture docs: compare `docs/docs/{queries,architecture}/**/*.md` with the applicable operation headers/tests; batch at most three small docs or one large doc. +- [ ] P-44 — api-doc-consistency × storage/IPC docs: compare `docs/docs/storage/*.md` and namespace docs for `.db`, `.csv`, `.ipc`, `.log` with headers/registrations/tests; split by namespace. +- [ ] P-45 — language-semantics × errors/nulls: compare `docs/docs/guides/errors.md`, public error/null declarations, and `test/rfl/{null,expr,regress}/**/*.rfl`; split by error then null behavior. +- [ ] P-46 — test-adequacy × memory/core C tests: audit `test/test_{arena,buddy,cow,heap,heap_parallel,block,morsel,pool,platform,progress,runtime,types}.c` against the corresponding contracts; batch related small tests. +- [ ] P-47 — test-adequacy × language/runtime C tests: audit `test/test_{compile,err,format,lang,repl,term,numparse}.c`; split `test_lang.c` by symbol ranges. +- [ ] P-48 — test-adequacy × execution/query C tests: audit `test/test_{exec,opt,pipe,rowsel,group_extra,group_pushdown,join_buildside,window,sort,idx_route,partition_exec}.c`; split large tests by subsystem. +- [ ] P-49 — test-adequacy × storage/I/O C tests: audit `test/test_{aof,csv,ipc,journal,meta,splay,store,public_api}.c` against persistence and API contracts. +- [ ] P-50 — test-adequacy × values/tables C tests: audit `test/test_{atom,dict,domain,f64_nullmodel,list,sel,str,sym,table,vec}.c` against N01/N02/N03. +- [ ] P-51 — test-adequacy × graph/advanced C tests: audit `test/test_{csr,datalog,embedding,graph,graph_builtin,index,lftj,link,traverse,fused_topk,fvec}.c`; split large algorithm tests. +- [ ] P-52 — test-adequacy × stress/harness: audit `test/{main,stress_eval,stress_store,test_stress_eval,test_stress_matrix,test_stress_random,test_audit}.c` and CI discovery/gating. +- [ ] P-53 — test-adequacy × Rayfall scalar/language: audit `test/rfl/{arith,cmp,lang,type,temporal,strop}/**/*.rfl` for meaningful assertions and reference coverage; split by directory. +- [ ] P-54 — test-adequacy × Rayfall collections/nulls: audit `test/rfl/{collection,dict,hof,null,ops,symbol,table}/**/*.rfl`; split by directory. +- [ ] P-55 — test-adequacy × Rayfall aggregation/query: audit `test/rfl/{agg,group,query,pivot}/**/*.rfl`; split by directory. +- [ ] P-56 — test-adequacy × Rayfall joins/order/windows: audit `test/rfl/{join,linkop,sort,window}/**/*.rfl`; split by directory. +- [ ] P-57 — test-adequacy × Rayfall persistence/system: audit `test/rfl/{io,journal,storage,store,system,mem}/**/*.rfl`; split by directory. +- [ ] P-58 — test-adequacy × Rayfall graph/Datalog/vector: audit `test/rfl/{graph,datalog,embedding}/**/*.rfl`; split by directory. +- [ ] P-59 — test-adequacy × integration/regress/optimizer: audit `test/rfl/{integration,regress,opt,fused,lazy}/**/*.rfl`; split integration by subject and record untested contracts. +- [ ] P-60 — resilience-security × fuzz surfaces: compare all six fuzz drivers, `fuzz/README.md`, seeds/dictionaries, and CI/Makefile discovery; one driver/surface per subpass. +- [ ] P-61 — build-release × build flavors/portability: audit `Makefile`, `CONTRIBUTING.md`, `.clang-tidy`, `.tsan-suppressions`, and `.github/workflows/{ci,nightly}.yml` for source coverage and semantic/gate parity. +- [ ] P-62 — build-release × version/release/packaging: audit `RELEASE.md`, release/version workflows and scripts, `packaging/*`, and install claims in README; split release and packaging paths. +- [ ] P-63 — api-doc-consistency × examples: compile/compare `examples/*.c` and `examples/rfl/**/*.rfl` against the referenced APIs and README/docs; batch at most three examples. +- [ ] P-64 — docs-structure × navigation/redirects: audit `mkdocs.yml`, docs indexes, `docs/llms.txt`, redirect stubs, and link targets for promised reachability and orphan/duplicate content. +- [ ] P-65 — docs-structure × site shells: compare `overrides/**`, authored `website/*.{html,css,js}`, docs root pages, and Pages workflow for route/navigation/brand consistency. +- [ ] P-66 — performance-contract × architecture/benchmarks: compare README and architecture performance/resource claims with `bench/**` scenario setup and the implementation path each benchmark invokes; split by benchmark directory. + +## 5. Limits + +max_findings_per_pass: 15 +remediation_batch_size: 8 +class_threshold: 3 +reopen_limit: 2 + +No graph assistance was present during planning. Cross-unit relationships are +encoded directly in the API/doc, implementation/test, and build/workflow +passes above. diff --git a/audit/LEDGER.md b/audit/LEDGER.md new file mode 100644 index 000000000..46f437856 --- /dev/null +++ b/audit/LEDGER.md @@ -0,0 +1,13 @@ +| id | title | severity | status | next | updated | +|---|---|---|---|---|---| +| F-0001 | Core lifecycle example does not compile against its documented header | major | fixed | Verifier | 2026-07-22 | +| F-0002 | Documentation and public API tests release errors with a no-op helper | major | superseded-by-class | — | 2026-07-22 | +| F-0003 | ray_eval_str null-return contract contradicts the public value invariant | major | superseded-by-class | — | 2026-07-22 | +| F-0004 | Public null-sentinel contract contradicts its own predicates | major | superseded-by-class | — | 2026-07-22 | +| F-0005 | Time atom documentation uses nanoseconds for a millisecond value | major | fixed | Verifier | 2026-07-22 | +| F-0006 | Public API test redeclares runtime functions instead of testing the header | minor | superseded-by-class | — | 2026-07-22 | +| F-0007 | Public date test labels a different epoch than the API reference | minor | fixed | Verifier | 2026-07-22 | +| CF-0001 | Public error-cleanup examples use ray_release on owned errors | major | fixed | Verifier | 2026-07-22 | +| CF-0002 | ray_eval_str comments still claim value-null is bare NULL | major | fixed | Verifier | 2026-07-22 | +| CF-0003 | Null-encoding comments conflict across scalar and vector surfaces | major | fixed | Verifier | 2026-07-22 | +| CF-0004 | Runtime API is redeclared after including the public header | minor | accepted | human | 2026-07-22 | diff --git a/audit/XCHECK.md b/audit/XCHECK.md new file mode 100644 index 000000000..1121ba908 --- /dev/null +++ b/audit/XCHECK.md @@ -0,0 +1,228 @@ +# XCHECK — Cross-Agent Audit & Remediation Methodology + +version: 0.1.2 + +Self-contained. If you are an agent reading this inside a project's `audit/` +directory, this file plus `AUDIT.md` plus your charter is everything you need. +Do not read the whole project "for context" — the Session Protocol (§4) tells +you exactly what to read. + +Language rule: this methodology and all templates are English. Findings, +plans, and reports are written in the operator's working language — pick one +per audit and stay consistent. Evidence quotes are always verbatim +in the original language of the material. + +## 1. System Overview + +The methodology runs one cycle, repeatedly, over a project of any size: `audit → human triage → remediate → verify`. An Auditor finds defects and files them as evidence-backed findings. A human triages them. A Remediator fixes the accepted ones. A Verifier gives each fix a binding verdict. Findings that survive verification close; findings that don't reopen and go back to remediation. + +The cycle exists because the obvious alternative — one long free-form agent session working through a large project — fails predictably. Long sessions lose the thread: they forget earlier decisions, invent particulars they never actually checked, and hallucinate both findings and fixes. This methodology substitutes three concrete mechanisms for confidence in an agent's memory: narrow chartered sessions that never run long enough to lose the thread, findings anchored to evidence that can be mechanically re-checked, and cross-agent verification where the fixer is never the judge. + +Architecture, in short: a findings ledger plus a finding state machine (§5) is the skeleton — durable, file-based state that outlives any single session. Per-session narrow charters (§4) are the working discipline — what makes each individual session reliable even though it starts from a blank context every time. + +Universality is a property of one boundary, and it is load-bearing: *"XCHECK.md defines mechanics and knows nothing about domains; AUDIT.md instantiates the methodology for one concrete project."* Everything in this file must hold for a project made of code and a project made of text, unchanged. Anything that depends on what kind of material is being audited belongs in AUDIT.md, not here. + +## 2. Artifacts + +A **unit** is one addressable piece of the material — one file, or one section of continuous text. A **dimension** is one audit angle, always backed by a norm (§6). AUDIT.md defines both concretely for a given project; this file only assumes they exist. + +The methodology's state lives entirely in the `audit/` directory of the audited project: + +``` +audit/ + XCHECK.md # copy of this file — the audit is self-contained + AUDIT.md # charter: dimensions, unit map, pass queue, norms, limits + LEDGER.md # index: one row per finding + templates/ # artifact skeletons (finding, class-finding, pass, plan) — copy the matching one when creating a new artifact file + findings/ + F-0001-.md # one finding, one file, full biography inside + CF-0001-.md # one class finding + passes/ + P-01-.md # one pass report: what was covered, what was not + plans/ + RP-0001.md # remediation plan for one batch of findings +``` + +IDs: `F-NNNN`, `CF-NNNN`, `RP-NNNN` are 4 digits. `P-NN` is 2 digits. Slugs: ASCII, +lowercase, hyphenated English gist of the title (titles themselves may be in the operator's working language). + +A finding file's frontmatter is the canonical record: + +```yaml +id: F-0001 # or CF-0001 for a class finding +title: +severity: critical | major | minor | info +dimension: +unit: # a YAML list for a cross-unit finding +status: reported +class: null | CF-NNNN # which class finding, if any, absorbed this one +attempts: 0 # incremented on each reopened cycle +pass: P-01 # which pass discovered it +updated: +``` + +`LEDGER.md` rows follow one fixed format: `| id | title | severity | status | next | updated |`. `next` names whichever role or the human is expected to act on the finding next: a canonical role name, `human`, or `—` for terminal states. The Verifier appends `⚠ needs-human` in the `next` column when `reopen_limit` consecutive reopenings are reached. + +Ownership rules: + +1. A finding file is the single source of truth for that finding. Everything about it — evidence, validation, remediation, verification — lives in that one file. +2. `LEDGER.md` is a derived index, not a second copy. One row per finding, nothing more. +3. The finding file is the source of truth for every status except pending triage decisions. Triage decisions live in LEDGER.md first: when a ledger row shows a triage status that is a legal triage transition from the file's frontmatter status (`reported` → `accepted`/`rejected`/`deferred`; member reversion to `accepted` on a rejected CF), the next agent session that touches that finding applies the decision to the file before any other work. Applying a pending triage decision is executing the human's call, not taking ownership of a triage status (§5). For all other disagreements, fix the ledger to match the file. + +## 3. Roles + +A role is a function, not an agent identity. The default assignment is Codex for Planner, Auditor, and Verifier, and Claude for Remediator — but any role can be played by any capable agent, and a third agent can join without changing this file. One rule is hard: **the Verifier is never the agent or session that produced the fix it verifies.** Prefer a different agent; the minimum acceptable substitute is a different, fresh session of the same agent. + +The only mandatory human step in the cycle is Triage. Everything else runs agent-to-agent through files. + +### Planner +- **Mission:** inventory the project and its norms; produce dimensions, a unit map, and a pass queue. +- **Default agent:** Codex, one session. +- **Reads:** XCHECK.md; the project's normative documents; a structural listing of the project. Not the full content of every unit. +- **Writes:** AUDIT.md. +- **Stop conditions:** AUDIT.md exists, with at least one dimension backed by a norm and a non-empty pass queue. +- **Forbidden:** filing findings; reading unit content end to end "to get a feel for it" — inventory only. + +### Auditor +- **Mission:** run one pass — one dimension against a batch of units — and record findings; also give disputed findings one written round of objection. +- **Default agent:** Codex, one session per pass (or a split of one, §4 rule 4). +- **Reads:** XCHECK.md; AUDIT.md; the pass charter; the units in the charter's scope; the norm the charter cites; templates/pass.md and templates/finding.md, copied when starting a pass or filing a finding. +- **Writes:** `findings/`, `passes/P-NN-.md`, new rows in LEDGER.md; the AUDIT.md pass queue — tick the completed pass's checkbox; append split remainders as new queued passes; one round of written objection in a finding file when the Remediator disputes it (§5, §9 rule 3). +- **Dedup rule:** before filing a finding, check it against ledger titles only — not finding bodies, not memory of earlier passes. A matching title gets new evidence appended to the existing finding, not a duplicate. +- **Stop conditions:** charter's unit range exhausted, or `max_findings_per_pass` (§10) reached, or context budget exhausted. +- **Forbidden:** fixing anything; exceeding the charter's unit range instead of splitting it; filing a finding that does not meet the Evidence Standard (§6). + +### Triage +- **Mission:** decide which findings get worked. +- **Default agent:** human. The one mandatory gate in the cycle. +- **Reads:** LEDGER.md; finding files for any finding whose title alone doesn't settle the call. +- **Writes:** the status column in LEDGER.md only — `reported` to `accepted`, `rejected`, or `deferred`. Batch decisions are allowed, e.g. all `critical` and `major` findings to `accepted` in one action. Rejecting a CF also reverts every finding in its `members:` list to `accepted` (write the ledger rows; agents apply to files per §2). +- **Stop conditions:** every `reported` row under review has a decision. +- **Forbidden:** editing finding file body sections — frontmatter sync for triage decisions is carried out by agent sessions per §2; setting any status other than `accepted`, `rejected`, or `deferred`. +- **Agent-as-pen:** the human may run triage through an interactive agent session. The agent presents findings and records the human's stated decisions in LEDGER.md verbatim; the decisions remain the human's. An agent must never accept, reject, or defer a finding on its own judgment, and must not batch-infer decisions the human did not state ("everything else rejected" counts only if the human said it). + +### Remediator +- **Mission:** fix a batch of accepted findings. +- **Default agent:** Claude, one or more sessions. +- **Reads:** XCHECK.md; AUDIT.md; the charter (a list of finding IDs); the finding files in scope; templates/plan.md and templates/class-finding.md, copied when opening a remediation plan or a class finding. +- **Writes:** `plans/RP-NNNN.md`; edits to the project material; the Validation and Remediation sections of each finding file; LEDGER.md. +- **Mandatory in-session sequence:** **validate → census → plan → fix → self-check.** No step is skipped or reordered (§7, §8 rule 1). Self-check means re-running the finding's own "how to verify" procedure before marking it fixed — catching what the Verifier would catch anyway, before it costs a reopen cycle. +- **Stop conditions:** every finding in the charter reaches `fixed`, `disputed`, `obsolete`, or `superseded-by-class`; or `remediation_batch_size` (§10) is exhausted; or context budget exhausted. +- **Forbidden:** touching a finding outside the charter; marking a finding `fixed` without a Remediation section naming what changed and where; verifying its own fix. + +### Verifier +- **Mission:** give a binding verdict on a batch of fixed findings. +- **Default agent:** Codex, one or more sessions. +- **Reads:** XCHECK.md; AUDIT.md; the charter; the finding files in scope; the diffs or changes the Remediation section points to. +- **Writes:** the Verification section of each finding file; LEDGER.md (`closed` or `reopened`). +- **Hard rule:** never the agent or session that produced the fix. **Adversarial stance:** *"Your job is to prove the fix wrong, not to confirm it."* +- **Stop conditions:** every finding in the charter has a verdict. +- **Forbidden:** closing a finding without running its "how to verify" procedure; skipping the adversarial check of the surrounding change; verifying its own fix. + +## 4. Session Protocol + +Eight rules. They apply to every session, in every role. + +1. **Charter required.** No session starts without an exact scope and stop conditions — e.g. "dimension X, units 4–6, stop after 15 findings" for an Auditor, or "findings F-0012..F-0019" for a Remediator. +2. **Fresh eyes.** State comes only from files. A session does not inherit conclusions from earlier sessions by memory. Anything a session acts on is re-verified against its source at the moment it is touched, regardless of what any prior session recorded about it. +3. **Explicit reading list.** Read XCHECK.md, AUDIT.md, your charter, the files the ledger points you to, and the artifact templates you instantiate. Nothing else. Reading the whole project "for context" is forbidden. +4. **Stop conditions are sacred.** Scope overflow: split the charter and write the remainder into the pass queue in AUDIT.md; skimming through to the end anyway is forbidden. Context running out: record what was covered and what was not, then exit cleanly. +5. **Coverage report is mandatory.** Every pass states explicitly what it did not cover. A silent gap is a protocol violation, not an acceptable shortcut. +6. **No finding quota.** Caps (§10) are upper bounds, not targets, and there is no minimum. A clean pass — zero findings — is a valid result and is recorded as one. +7. **Role launch is a one-liner.** A human starts a session with a single line: `Read audit/XCHECK.md. Role: . Charter: .` +8. **One active writing session per project at a time.** Coordination in v1 is manual: one human relays the baton between sessions. Concurrent writers are out of scope. Launcher tooling and scripted chains must serialize the same way: never start a writing session while another is active. + +## 5. Finding Lifecycle + +``` +reported → (Triage) → accepted | rejected | deferred +accepted → (Remediator) → validated | disputed | obsolete +validated → planned → fixed +fixed → (Verifier) → closed | reopened +reopened → back to the Remediator (planned → fixed again), attempts += 1 +disputed → (Auditor/human) → accepted | withdrawn +any status → (Remediator) → superseded-by-class (absorbed by a CF finding, §8) +superseded-by-class → (Triage) → accepted (CF rejected at triage, §8 rule 4) +``` + +| From | Event / actor | To | +|---|---|---| +| `reported` | Triage decides | `accepted`, `rejected`, or `deferred` | +| `accepted` | Remediator confirms the evidence | `validated` | +| `accepted` | Remediator cannot confirm the evidence | `disputed` | +| `accepted` | Remediator finds the defect already gone | `obsolete` | +| `validated` | Remediator writes a plan | `planned` | +| `planned` | Remediator applies the fix | `fixed` | +| `fixed` | Verifier confirms | `closed` | +| `fixed` | Verifier rejects | `reopened` | +| `reopened` | returns to the Remediator | `planned`, then `fixed` again; the Remediator increments `attempts` | +| `disputed` | Auditor or human sides with the finding | `accepted` | +| `disputed` | Auditor or human sides against the finding | `withdrawn` | +| any | Remediator opens a class finding for its pattern | `superseded-by-class` | +| `superseded-by-class` | Triage rejects the CF | `accepted` | + +Status ownership is the rule that keeps five roles from stepping on each other: *"One status, one owner: triage statuses are changed only by the human; validated/planned/fixed only by the Remediator; closed/reopened only by the Verifier; disputed resolution by the Auditor or the human. Never touch a status you do not own."* + +Definitions: + +- **`obsolete`** — validation shows the defect is already gone: an unrelated edit removed it before remediation touched it. +- **`disputed`** — the quote is not found in the material, or the problem is not substantively confirmed. +- **`reopen_limit` breach** — `reopen_limit` (default 2, §10) consecutive `reopened` verdicts on the same finding set the ledger flag `⚠ needs-human`; the automatic cycle stops and a human decides what happens next. +- **`withdrawn`** — a `disputed` finding resolved against the Auditor; it closes without a fix. +- **`superseded-by-class`** — set immediately on every member finding when a class finding is opened for its pattern (§8 rule 4); reverts to `accepted` if the human rejects the CF at triage. + +## 6. Evidence Standard + +Evidence is the anti-hallucination mechanism at the center of this methodology. Six rules govern every finding: + +1. **The quote is the primary anchor.** Character-exact, findable by a literal search in the material. A line number is a hint, not an anchor — text moves as the material is edited. +2. **A norm reference is mandatory.** A finding is a discrepancy between the material and a norm: a style contract, a spec, an ADR, a sourced fact, or internal consistency — in which case the contradicting passage is itself quoted as the norm. No norm, no finding: it is an opinion, and its severity is capped at `info`. +3. **"How to verify the fix" is written by the Auditor at creation time**, while the defect is in front of them. It is never written by the Verifier after the fact. +4. **A finding without evidence meeting this standard is invalid by construction.** It must not reach Triage. +5. **Locators follow the material kind.** Code: `file:line` plus the enclosing symbol name. Text: file, section, and the quote itself. +6. **Auditor-authored sections of a finding are append-only for other roles.** Evidence, Why this is a defect, and How to verify the fix are written once, by the Auditor; other roles may add to them but not rewrite them. The only in-place edit allowed is a locator update (§9 rule 1). + +## 7. Validation & Verification + +Two checks apply the Evidence Standard at two different points in the cycle. + +**Validation** is the mandatory first step of remediation (§3). Before planning a fix, the Remediator confirms the finding against the source: the quote exists, and the problem is real. This is close to free — the source has to be read to plan the fix anyway. Failure routes the finding to `disputed` (problem not confirmed) or `obsolete` (defect already gone). + +**Verification** is the Verifier's binding verdict on a `fixed` finding, and it has two parts. First, run the procedure the Auditor wrote in "How to verify the fix" against the current material. Second, adversarially inspect the surroundings of the change for collateral damage that procedure alone would not catch. The verdict is `closed` or `reopened`. Evidence for a `reopened` verdict is held to the same Evidence Standard (§6) as an original finding — an exact quote and a norm, not an impression. When a fix installed an automated guard, verify the guard adversarially: plant the defect it claims to catch in a temporary copy and expect the guard to fail it — a guard that passes its own selftest but not a live mutation is a hole, not a defense. + +## 8. Class Escalation + +A finding may be one instance of a systematic defect rather than a one-off. Escalation exists to catch that and fix it globally instead of one instance at a time. + +1. **Census is the mandatory second step of remediation**, after validation and before planning. The Remediator formulates the finding's pattern and searches for it across the whole project — not only in the finding's own unit. The search procedure fits the material (a literal-text or pattern search across files; a targeted read across sections where the pattern could recur) and must be recorded precisely enough for someone else to rerun it. +2. **Escalation threshold: `class_threshold` instances (default 3, §10).** Below threshold, fix the instance found and note its siblings in the finding file. At or above threshold, open a class finding. +3. **A class finding (`CF-NNNN`) contains:** an exact definition of the pattern and its search procedure; a full census of instances with locators, including any the original pass missed; the root cause — most often a norm that is missing, ambiguous, or inconsistently enforced; and a global strategy on an increasing scale: + - (a) fix every instance; + - (b) (a), plus fix the norm so the class cannot recur; + - (c) (b), plus an automated guard where one is possible — a search check, a lint rule, a style script — so the class becomes structurally hard to reintroduce. +4. **A CF finding passes through the same Triage gate** as any other finding — status `reported`, the human decides. The single human gate does not multiply, but a global change never bypasses it. On creation, every member finding moves to `superseded-by-class` immediately; if the human rejects the CF at triage, members revert to `accepted` for point fixes instead. The Remediator continues the current session with the rest of the batch — the CF waits for the next triage round. +5. **Class verification is a re-census**, performed by the Verifier: rerun the recorded search procedure and expect zero instances, or explicitly documented exceptions. This is what catches "fixed 12 of 15." +6. **Norm write-back is part of the fix, not a side effect.** Strategies (b) and (c) edit the project's normative documents in the same remediation, not as optional follow-up work. If the project maintains a persistent agent-memory or lessons store, record the norm change there in the same remediation. +7. **The five-step Remediator sequence maps onto an accepted CF as follows:** validate = re-run the recorded census procedure; census = already done (the CF's census IS the scope); plan = the Global strategy section; fix as usual; self-check = re-run the census procedure expecting zero instances or documented exceptions. +8. **Norm ratification gate.** If a class fix under strategy (b) or (c) changes a norm, relies on a norm that another normative source contradicts (including machine registries and configuration files), or must pick a side in any conflict between norms — the Remediator stops after writing the plan and routes the conflict to the norm owner through triage before executing. Documenting a norm conflict and proceeding anyway is a protocol violation for class fixes: a class fix in the wrong direction multiplies one error across the whole corpus. + +## 9. Failure Modes + +1. **Stale finding.** The material moved since the finding was filed, and the quote is no longer found by a literal search. Re-locate by meaning. If the defect is gone, mark `obsolete`. If it moved, update the locator and proceed. Fixing "from memory of where it used to be" is forbidden. +2. **Non-converging cycle.** `reopen_limit` (§10) consecutive `reopened` verdicts on one finding set `⚠ needs-human` in the ledger (§5). The automatic cycle does not keep spinning on its own. +3. **Agent dispute.** The Auditor insists on a finding the Remediator marked `disputed`. One written round of objection in the finding file, then the human decides. Ping-pong between agents is forbidden. +4. **Ledger drift.** The finding file is truth; LEDGER.md is a derived index (§2). Whoever notices a mismatch fixes the ledger silently — except a pending triage decision, which flows the other way: apply it to the file per §2 rule 3. +5. **Charter overflow.** The remainder of an overflowing charter becomes a new queued pass in AUDIT.md (§4 rule 4). A session interrupted mid-charter must still leave a coverage report (§4 rule 5) — otherwise the pass counts as not done at all. +6. **Concurrent human edits to the material.** Not forbidden. The Evidence Standard (§6) self-protects: a quote either still matches, or the finding falls into the stale-finding path above. +7. **Git.** If the project is under git: remediation commits reference finding IDs, and verification reads diffs. If not: the finding's Remediation section lists the files or sections changed. Git is an amplifier, not a requirement. + +## 10. Configuration Defaults + +| Key | Default | Meaning | +|---|---|---| +| `max_findings_per_pass` | 15 | Upper bound on findings recorded by one Auditor pass (§4 rule 6). | +| `remediation_batch_size` | 8 | Upper bound on findings worked by one Remediator session (§3). | +| `class_threshold` | 3 | Minimum instance count that escalates a finding to a class finding (§8 rule 2). | +| `reopen_limit` | 2 | Consecutive `reopened` verdicts on one finding before it is flagged `⚠ needs-human` (§5, §9 rule 2). | + +AUDIT.md may override any of these defaults per project. diff --git a/audit/findings/CF-0001-public-error-cleanup-uses-ray-release.md b/audit/findings/CF-0001-public-error-cleanup-uses-ray-release.md new file mode 100644 index 000000000..fef684dc6 --- /dev/null +++ b/audit/findings/CF-0001-public-error-cleanup-uses-ray-release.md @@ -0,0 +1,99 @@ +--- +id: CF-0001 +title: Public error-cleanup examples use ray_release on owned errors +severity: major +dimension: api-doc-consistency +unit: + - docs/docs/c-api/core.md + - docs/docs/c-api/dag.md + - test/test_public_api.c +status: fixed +class: null +members: + - F-0002 +attempts: 0 +pass: P-01 +created-by: RP-0001 +updated: 2026-07-22 +--- + +## Pattern + +An owned `RAY_ERROR` in a public C API reference example or the +public-header conformance test is cleaned up with `ray_release`, even though +the public ownership contract says that helper is a no-op for errors and that +the owner must call `ray_error_free`. + +Reproduce the census with: + +```sh +rg -n -C 12 'ray_release\((result|err)\)' docs/docs/c-api test/test_public_api.c +``` + +For every match, retain it only when the same example/test has already proved +the released value is an error with `RAY_IS_ERR`. The search covers the whole +public C API reference tree and its public-header conformance test. + +## Census + +- `docs/docs/c-api/core.md:490-492`, the `RAY_IS_ERR(p)` example releases the + owned `result` error. +- `docs/docs/c-api/dag.md:373-375`, the `ray_execute` example releases the + owned `result` error. +- `test/test_public_api.c:580-592`, + `test_public_get_error_trace_populated` releases the owned `err` error. +- `test/test_public_api.c:601-604`, + `test_public_get_error_trace_cleared_on_eval` releases the owned `err` + error. + +Four instances meet the project threshold of three. + +## Validation + +Re-ran the recorded census on 2026-07-22: + +```sh +rg -n -C 12 'ray_release\((result|err)\)' docs/docs/c-api test/test_public_api.c +``` + +All four recorded instances remain eligible: each value is proved to be a +`RAY_ERROR` before cleanup. `include/rayforce.h` documents +`ray_error_free` as the owned-error cleanup and `src/mem/cow.c` confirms that +`ray_release` deliberately returns without freeing errors. The trace returned +by `ray_get_error_trace` is separately owned by the VM, so freeing the error +result does not invalidate either public trace test. + + +## Root cause + +The public error-ownership norm in `include/rayforce.h` was added without a +complete update of the public examples and conformance tests that demonstrate +error cleanup. + +## Global strategy + +Rung (a): replace all four cleanup calls with `ray_error_free`, then run the +public API test and rebuild the documentation. Re-run the recorded census and +require zero public-contract instances. + +## Remediation + +Planned in `audit/plans/RP-0002.md` using the accepted rung-(a) global +strategy. No ownership norm changes or conflicts require ratification. + +Replaced all four eligible cleanup calls with `ray_error_free`: the +`RAY_IS_ERR` examples in `docs/docs/c-api/core.md` and +`docs/docs/c-api/dag.md`, plus both error-trace cases in +`test/test_public_api.c`. + +Self-checks completed on 2026-07-22: + +- The recorded census returned only three success-path `ray_release(result)` + calls guarded by `!RAY_IS_ERR`; zero eligible class instances remain. +- `make test` passed 3638 of 3638 tests with 0 skipped and 0 failed. +- `mkdocs build --strict` completed successfully. + + +## Verification + +Pending independent Verifier review. diff --git a/audit/findings/CF-0002-ray-eval-str-null-contract-stale.md b/audit/findings/CF-0002-ray-eval-str-null-contract-stale.md new file mode 100644 index 000000000..7a5a28cea --- /dev/null +++ b/audit/findings/CF-0002-ray-eval-str-null-contract-stale.md @@ -0,0 +1,99 @@ +--- +id: CF-0002 +title: ray_eval_str comments still claim value-null is bare NULL +severity: major +dimension: api-doc-consistency +unit: + - include/rayforce.h + - test/main.c + - test/stress_eval.c +status: fixed +class: null +members: + - F-0003 +attempts: 0 +pass: P-01 +created-by: RP-0001 +updated: 2026-07-22 +--- + +## Pattern + +A comment describing `ray_eval_str` says a void/null value is represented by +bare C `NULL`, contradicting the public value invariant and current evaluator, +which use `RAY_NULL_OBJ`. + +Reproduce the census with: + +```sh +rg -n -i 'ray_eval_str.*(returns? NULL|NULL = void/null|void results)' \ + include src docs test README.md examples fuzz bench +``` + +## Census + +- `include/rayforce.h:667`, the public declaration says “Returns NULL for + void / null results”. +- `test/main.c:247-248`, the Rayfall harness says `ray_eval_str` returns NULL + for void results and describes bare-NULL comparison semantics. +- `test/stress_eval.c:69`, the stress harness annotates the result with + “NULL = void/null result, fine”. + +Three instances meet the project threshold of three. + +## Validation + +Re-ran the recorded census on 2026-07-22. It reproduced the two single-line +harness comments but missed the public-header instance because `ray_eval_str` +and “Returns NULL” are on separate lines. A supplementary exact-literal +census reproducibly finds all three recorded instances: + +```sh +rg -n -i 'Returns NULL for void / null results|ray_eval_str returns NULL for void results|NULL = void/null result' \ + include src docs test README.md examples fuzz bench +``` + +The public invariant in `include/rayforce.h` states that value-null is always +`RAY_NULL_OBJ`; `test_eval_println` and `test_eval_null_keyword` assert that +behavior directly. All three stale comments therefore remain substantively +confirmed. + + +## Root cause + +The evaluator's null-result contract moved to the `RAY_NULL_OBJ` value +invariant, but comments in the public header and two harnesses were not migrated +with it. + +## Global strategy + +Rung (b): update all three comments to the `RAY_NULL_OBJ` contract and make +the public invariant the named source of truth. Retain defensive bare-NULL +checks where they protect against allocation failure, but do not describe bare +NULL as a successful evaluation result. Re-run the focused null-evaluation +test and the recorded census. + +## Remediation + +Planned in `audit/plans/RP-0003.md` using the human-accepted rung-(b) +strategy. The direction is the existing public `RAY_NULL_OBJ` invariant; no +new or different norm choice is introduced. + +Updated the public `ray_eval_str` contract in `include/rayforce.h` and the +harness comments in `test/main.c` and `test/stress_eval.c` to identify +`RAY_NULL_OBJ` as the successful void/null result. Defensive bare-NULL paths +remain unchanged and are described only as no-result fallbacks. + +Self-checks completed on 2026-07-22: + +- The supplementary exact-literal census returned zero stale instances. +- The original regex returned one documented non-defect exception: the + corrected `test/main.c` comment, because its broad `void results` alternative + also matches `RAY_NULL_OBJ` wording. +- Focused tests `lang/eval/println` and `lang/eval/null_keyword` each passed + 1/1 with no skips or failures. + + +## Verification + +Pending independent Verifier review. diff --git a/audit/findings/CF-0003-null-encoding-comments-conflict.md b/audit/findings/CF-0003-null-encoding-comments-conflict.md new file mode 100644 index 000000000..8ecf42d79 --- /dev/null +++ b/audit/findings/CF-0003-null-encoding-comments-conflict.md @@ -0,0 +1,138 @@ +--- +id: CF-0003 +title: Null-encoding comments conflict across scalar and vector surfaces +severity: major +dimension: api-doc-consistency +unit: + - include/rayforce.h + - src/vec/atom.c + - src/vec/vec.c + - src/table/domain.h + - src/ops/pivot.c + - docs/docs/namespaces/csv.md + - test/stress_store.c + - test/test_dict.c + - test/test_format.c + - test/test_str.c + - test/rfl/integration/null.rfl + - test/rfl/strop/split.rfl +status: fixed +class: null +members: + - F-0004 +attempts: 0 +pass: P-01 +created-by: RP-0001 +updated: 2026-07-22 +--- + +## Pattern + +A comment or API-facing statement assigns null status to empty SYM/STR values, +or says F32 has no sentinel, while the public atom/vector predicates and +current focused tests say SYM/STR empty values are ordinary values and F32 +uses NaN. + +Reproduce the candidate census with: + +```sh +rg -n -i '(SYM null\s*=|STR null\s*=|empty string IS.*null|empty symbol.*null|BOOL/U8/F32|types without a sentinel|global id 0.*null|id 0 is the SYM null)' \ + include src docs test README.md examples fuzz bench +``` + +Classify each candidate against `ray_atom_is_null_fn` in +`include/rayforce.h`, `ray_vec_is_null` in `src/vec/vec.c`, and the explicit +`nil?` expectations in `test/rfl/null/sym_no_null.rfl`. + +## Census + +- `include/rayforce.h:441-442` assigns SYM id 0 and empty STR as null. +- `include/rayforce.h:451-452` incorrectly groups F32 with types lacking a + sentinel. +- `src/vec/atom.c:185-186` repeats the F32 no-sentinel claim. +- `src/table/domain.h:50-51` calls global SYM id 0 null. +- `src/ops/pivot.c:1268-1269` calls SYM id 0 the SYM null. +- `src/vec/vec.c:1156-1157` calls an empty STR descriptor a STR null. +- `src/vec/vec.c:1200` repeats “STR null = empty string”. +- `docs/docs/namespaces/csv.md:25` says empty strings are read as null and + marked `RAY_ATTR_HAS_NULLS`. +- `test/stress_store.c:118` labels the empty symbol as a SYM null. +- `test/test_dict.c:1177-1179` says empty STR is a null atom. +- `test/test_dict.c:1214` repeats the STR-null/empty-string conflation. +- `test/test_format.c:751-753` calls the empty string and “null string” the + same null-like value, despite the predicate returning false. +- `test/test_str.c:1830` says a STR null is stored as an empty string. +- `test/rfl/integration/null.rfl:8` says “STR null = empty string” immediately + before an expectation that `(nil? "")` is false. +- `test/rfl/strop/split.rfl:4-5` says empty string is the STR null immediately + before an expectation that the resulting empty value is not null. + +Fifteen instances meet the project threshold of three. + +## Validation + +Re-ran the recorded candidate census on 2026-07-22 and manually classified +each result against `ray_atom_is_null_fn`, `ray_vec_is_null`, +`ray_vec_set_null_checked`, and the focused `nil?` expectations. All fifteen +recorded conflicting statements remain in their cited locations. The search +also returns correct counterexamples that explicitly say empty SYM/STR values +are not null; those are classification references, not defects. + +The current executable behavior is internally consistent enough to expose the +norm choice but not to authorize it: + +- F32 atoms and vectors use NaN as a null sentinel. +- SYM and STR atoms have no null distinct from their empty values; + `ray_typed_null` collapses to the ordinary empty value and + `RAY_ATOM_IS_NULL` returns false. +- SYM and STR vectors are likewise treated as non-nullable by + `ray_vec_is_null`; missing inputs collapse to empty payloads without + `RAY_ATTR_HAS_NULLS`. +- BOOL and U8 typed-null atoms use the auxiliary null bit, while BOOL and U8 + vectors reject null insertion. + +The conflict is therefore substantive and the finding is validated. Because +the Global strategy is rung (b) and explicitly requires the norm owner to +choose the cross-surface matrix, validation does not ratify either direction. + + +## Root cause + +The project lacks one ratified nullability matrix that distinguishes typed-null +construction, payload sentinels, empty-value collapse, and the public null +predicates. Comments from incompatible historical models coexist. + +## Global strategy + +Proposed rung (b): first obtain norm-owner ratification for a single per-type +matrix, because the current normative and executable surfaces conflict. Then +fix all census instances and write the ratified matrix into the public API +contract. The norm ratification gate applies: no global edit should execute +until triage records which semantics own SYM/STR empty values and F32. + +## Remediation + +The human norm owner ratified the recommended preserve-behavior matrix on +2026-07-22. Under `audit/plans/RP-0004.md`, the public contract in +`include/rayforce.h` now publishes the cross-surface matrix; all fifteen +censused statements in the header, implementation comments, documentation, +and tests now describe empty SYM/STR values and F32 consistently. The new +`public/nullability_matrix` test in `test/test_public_api.c` pins ordinary and +typed-null atom behavior for SYM, STR, BOOL, U8, and F32. Executable null +representation was not changed. + +Self-checks completed on 2026-07-22: + +- The recorded class census returned seven matches, all manually classified + as correct counterexamples that explicitly say empty SYM values are not + null; it returned zero conflicting statements. +- The ASan/UBSan debug build completed cleanly with warnings as errors. +- The full test suite passed 3,639/3,639 with zero skips and zero failures, + including `public/nullability_matrix` and every touched focused area. +- `mkdocs build --strict` completed successfully. +- `git diff --check` reported no whitespace errors. + + +## Verification + +Pending independent Verifier review. diff --git a/audit/findings/CF-0004-runtime-api-redeclared-after-public-header.md b/audit/findings/CF-0004-runtime-api-redeclared-after-public-header.md new file mode 100644 index 000000000..d268a41e3 --- /dev/null +++ b/audit/findings/CF-0004-runtime-api-redeclared-after-public-header.md @@ -0,0 +1,86 @@ +--- +id: CF-0004 +title: Runtime API is redeclared after including the public header +severity: minor +dimension: api-doc-consistency +unit: + - fuzz/common.h + - test/main.c + - test/test_compile.c + - test/test_datalog.c + - test/test_embedding.c + - test/test_format.c + - test/test_fused_topk.c + - test/test_ipc.c + - test/test_journal.c + - test/test_lang.c + - test/test_link.c + - test/test_public_api.c + - test/test_repl.c + - test/test_store.c + - test/test_term.c + - test/test_types.c +status: reported +class: null +members: + - F-0006 +attempts: 0 +pass: P-01 +created-by: RP-0001 +updated: 2026-07-22 +--- + +## Pattern + +A C translation unit directly includes `rayforce.h` and then locally repeats +the public `ray_runtime_t`, `ray_runtime_create`, or `ray_runtime_destroy` +declarations. Those repetitions mask removal or drift in the public header. + +Reproduce the census with: + +```sh +rg -l -0 '#include[[:space:]]*[<"]rayforce\.h[>"]' --glob '*.[ch]' . \ + | xargs -0 rg -n '(struct ray_runtime_s;|typedef struct ray_runtime_s ray_runtime_t;|extern[[:space:]]+ray_runtime_t\*[[:space:]]*ray_runtime_create|extern[[:space:]]+void[[:space:]]+ray_runtime_destroy)' +``` + +## Census + +- `fuzz/common.h:38-40` +- `test/main.c:54-57` +- `test/test_compile.c:40-43` +- `test/test_datalog.c:43` +- `test/test_embedding.c:45-48` +- `test/test_format.c:36-39` +- `test/test_fused_topk.c:51-54` +- `test/test_ipc.c:86-88` +- `test/test_journal.c:46-49` +- `test/test_lang.c:54-57` +- `test/test_link.c:50-53` +- `test/test_public_api.c:37-40` +- `test/test_repl.c:77-80` +- `test/test_store.c:60-62` +- `test/test_term.c:76-79` +- `test/test_types.c:32-35` + +Sixteen translation units meet the project threshold of three. + +## Validation + + +## Root cause + +The runtime declarations were historically internal and were copied into test +and fuzz harnesses. When they became public, the local compatibility blocks +were not removed and no guard prohibited redeclaring public API symbols. + +## Global strategy + +Rung (c): remove every redundant public runtime declaration while retaining +declarations of genuinely internal globals such as `__RUNTIME`; compile all +affected test/fuzz targets; and add a lightweight source check that rejects +future runtime API redeclarations in files that include `rayforce.h`. + +## Remediation + + +## Verification diff --git a/audit/findings/F-0001-core-lifecycle-example-does-not-compile.md b/audit/findings/F-0001-core-lifecycle-example-does-not-compile.md new file mode 100644 index 000000000..34777be54 --- /dev/null +++ b/audit/findings/F-0001-core-lifecycle-example-does-not-compile.md @@ -0,0 +1,103 @@ +--- +id: F-0001 +title: Core lifecycle example does not compile against its documented header +severity: major +dimension: api-doc-consistency +unit: + - docs/docs/c-api/core.md + - include/rayforce.h + - test/test_public_api.c +status: fixed +class: null +attempts: 0 +pass: P-01 +updated: 2026-07-22 +--- + +## Evidence + +In `docs/docs/c-api/core.md`, section “Headers” (line 4), the page says: + +> Include `` for the bulk of the public API. A few internal helpers documented here live in their own headers — in particular `ray_cow` and the `ray_heap_init` / `ray_heap_destroy` lifecycle calls are declared in `"mem/heap.h"`, not in ``. + +But its “Typical lifecycle pattern” (lines 42–54) contains only: + +```c +#include + +int main(void) { + ray_heap_init(); + ray_sym_init(); + /* ... use Rayforce API ... */ + ray_sym_destroy(); + ray_heap_destroy(); +``` + +Compiling that pattern as C17 with `cc -std=c17 -Werror -fsyntax-only +-Iinclude` fails with implicit declarations for both heap calls. + +The public lifecycle norm in `include/rayforce.h`, section “Runtime + Rayfall +Eval API” (lines 623–664), instead says: + +> Embedders build a runtime, optionally restoring a persisted symbol table, then evaluate Rayfall source strings against the global env. + +and declares: + +```c +ray_runtime_t* ray_runtime_create(int argc, char** argv); +void ray_runtime_destroy(ray_runtime_t* rt); +``` + +`test/test_public_api.c`, `test_public_runtime_create_with_sym_eval` (lines +406–426), also exercises the public runtime create/destroy lifecycle rather +than the undocumented heap declarations. + +## Why this is a defect + +The page's canonical lifecycle example fails under the project's C17/Werror +contract and steers embedders away from the lifecycle actually exported by the +public header and exercised by the public API test. + +## How to verify the fix + +Extract the revised “Typical lifecycle pattern” into a temporary `.c` file and +run `cc -std=c17 -Werror -fsyntax-only -Iinclude `. It must compile using +exactly the headers shown in the example, and its create/destroy pairing must +match a lifecycle declared in `include/rayforce.h` and exercised by +`test/test_public_api.c`. + +## Validation + +Validated. The quoted documentation and public declarations are still present. +Recompiling the documented pattern with +`cc -std=c17 -Werror -fsyntax-only -Iinclude` fails on implicit declarations +of `ray_heap_init` and `ray_heap_destroy`, while the public header and public API +tests expose the runtime create/destroy lifecycle. + +## Census + +Pattern: a standalone public C example calls `ray_heap_init` or +`ray_heap_destroy` without including `mem/heap.h`, or presents those internal +calls as the public embedder lifecycle. Searched with +`rg -n 'ray_heap_init|ray_heap_destroy|ray_runtime_create|ray_runtime_destroy' +README.md docs examples` and inspected every containing C example's include +set. The only defective standalone example is the “Typical lifecycle pattern” +in `docs/docs/c-api/core.md`; README and complete DAG examples include +`mem/heap.h`. One instance is below the class threshold. + +## Objection + + +## Remediation + +Replaced the “Typical lifecycle pattern” in `docs/docs/c-api/core.md` with the +public `ray_runtime_create`/`ray_runtime_destroy` lifecycle declared by +`rayforce.h`. The example checks runtime creation before use and no longer +calls undeclared internal heap functions. + +Self-check: extracted the revised snippet to a temporary C file and ran +`cc -std=c17 -Werror -fsyntax-only -Iinclude`; it exited 0. The full sanitized +suite also passed, 3638/3638. + + +## Verification diff --git a/audit/findings/F-0002-error-objects-released-with-no-op-helper.md b/audit/findings/F-0002-error-objects-released-with-no-op-helper.md new file mode 100644 index 000000000..0341a04fb --- /dev/null +++ b/audit/findings/F-0002-error-objects-released-with-no-op-helper.md @@ -0,0 +1,83 @@ +--- +id: F-0002 +title: Documentation and public API tests release errors with a no-op helper +severity: major +dimension: api-doc-consistency +unit: + - docs/docs/c-api/core.md + - test/test_public_api.c + - include/rayforce.h +status: superseded-by-class +class: CF-0001 +attempts: 0 +pass: P-01 +updated: 2026-07-22 +--- + +## Evidence + +`docs/docs/c-api/core.md`, section “RAY_IS_ERR(p)” (lines 488–493), teaches: + +```c +if (RAY_IS_ERR(result)) { + printf("Error: %s\n", ray_err_code(result)); + ray_release(result); + return 1; +} +``` + +`test/test_public_api.c` repeats the same cleanup in +`test_public_get_error_trace_populated` (line 592) and +`test_public_get_error_trace_cleared_on_eval` (line 604): + +```c +ray_release(err); +``` + +The ownership norm in `include/rayforce.h`, immediately above +`ray_error_free` (lines 237–241), says the opposite: + +> Free a RAY_ERROR object. ray_release() is a deliberate no-op for error ray_t* (see src/mem/cow.c), so callers that hold the sole reference and want the block reclaimed must use this helper instead — otherwise the error leaks until heap teardown. + +and declares: + +```c +void ray_error_free(ray_t* err); +``` + +## Why this is a defect + +The primary error-handling example and its public API tests normalize a cleanup +operation the public header explicitly says leaks. A long-lived embedder that +copies the example retains every error object until heap teardown. + +## How to verify the fix + +Search these units for cleanup of values already proven by `RAY_IS_ERR`: +`rg -n 'ray_release\((result|err)\)' docs/docs/c-api/core.md +test/test_public_api.c`. Every such owned error must be reclaimed according to +the `ray_error_free` contract, and the focused public API tests must still pass. + +## Validation + +Validated. All three quoted cleanup calls remain. `src/mem/cow.c`, +`ray_release`, still returns immediately for `RAY_IS_ERR(v)`, confirming that +the documented/test cleanup does not reclaim error objects and that +`ray_error_free` is required for owned errors. + +## Census + +The public-contract cleanup pattern recurs four times across the C API +reference and public-header conformance test. The full reproducible census is +recorded in CF-0001. Because four meets the configured class threshold of +three, this finding is superseded by CF-0001 pending human triage. + +## Objection + + +## Remediation + +Superseded by CF-0001 during RP-0001; no point fix was applied. + + +## Verification diff --git a/audit/findings/F-0003-eval-null-return-contract-contradicts-invariant.md b/audit/findings/F-0003-eval-null-return-contract-contradicts-invariant.md new file mode 100644 index 000000000..070ccd10c --- /dev/null +++ b/audit/findings/F-0003-eval-null-return-contract-contradicts-invariant.md @@ -0,0 +1,62 @@ +--- +id: F-0003 +title: ray_eval_str null-return contract contradicts the public value invariant +severity: major +dimension: api-doc-consistency +unit: include/rayforce.h +status: superseded-by-class +class: CF-0002 +attempts: 0 +pass: P-01 +updated: 2026-07-22 +--- + +## Evidence + +`include/rayforce.h`, immediately above `RAY_ASSERT_VALUE` (lines 208–210), +states the public value norm: + +> A value position (an eval'd / builtin-returned ray_t*) is never a bare C NULL — value-null is always RAY_NULL_OBJ. + +The contract immediately above `ray_eval_str` in the same header (lines +666–670) contradicts it: + +> Parse and evaluate a Rayfall source string against the global env. Returns NULL for void / null results, an error ray_t* on failure (test with RAY_IS_ERR and inspect with ray_err_code), or the result value otherwise. + +## Why this is a defect + +Embedders cannot determine from the public header whether an evaluated null is +tested with `p == NULL` or `RAY_IS_NULL(p)`. Those alternatives imply different +safe control flow because the surrounding API explicitly distinguishes bare C +NULL from `RAY_NULL_OBJ`. + +## How to verify the fix + +Re-read both quoted contracts and compile/run a focused public API test that +calls `ray_eval_str` on a void/null expression. The observed return and both +header comments must agree on exactly one representation; the test must assert +that representation explicitly. + +## Validation + +Validated. Both contradictory header comments remain. The current evaluator +returns `RAY_NULL_OBJ` for no-value paths, and the existing +`test_eval_println` assertion explicitly confirms `RAY_IS_NULL(result)`, so the +`ray_eval_str` comment is the stale side of the contract. + +## Census + +Exact searches for comments coupling `ray_eval_str` with bare NULL null/void +results found three instances: `include/rayforce.h:667`, `test/main.c:247-248`, +and `test/stress_eval.c:69`. The complete procedure is in CF-0002. Three meets +the class threshold, so this finding is superseded pending triage of CF-0002. + +## Objection + + +## Remediation + +Superseded by CF-0002 during RP-0001; no point fix was applied. + + +## Verification diff --git a/audit/findings/F-0004-null-sentinel-contract-contradicts-itself.md b/audit/findings/F-0004-null-sentinel-contract-contradicts-itself.md new file mode 100644 index 000000000..58202e963 --- /dev/null +++ b/audit/findings/F-0004-null-sentinel-contract-contradicts-itself.md @@ -0,0 +1,75 @@ +--- +id: F-0004 +title: Public null-sentinel contract contradicts its own predicates +severity: major +dimension: api-doc-consistency +unit: include/rayforce.h +status: superseded-by-class +class: CF-0003 +attempts: 0 +pass: P-01 +updated: 2026-07-22 +--- + +## Evidence + +`include/rayforce.h`, section “Null Sentinel Values” (lines 438–447), declares: + +> SYM null = sym ID 0; STR null = empty string (length 0); BOOL and U8 are non-nullable. + +and defines `NULL_F32`: + +```c +#define NULL_F32 ((float)__builtin_nanf("")) +``` + +The public `ray_atom_is_null_fn` contract and implementation directly below it +(lines 449–488) instead say: + +> types without a sentinel (BOOL/U8/F32) fall back to the aux[0]&1 bit written by ray_typed_null. + +and: + +> SYM has no null — sym 0 is the empty symbol ' (a value) ... A SYM atom is never null. + +> STR has no null distinct from "" ... A STR atom is never null — the empty string is a value. + +The function also has a dedicated `RAY_F32` NaN branch before the fallback. + +## Why this is a defect + +The same public section assigns incompatible null encodings to SYM, STR, BOOL, +U8, and F32. Callers using the declared sentinels can therefore disagree with +the header's own public predicate about whether a value is null. + +## How to verify the fix + +For every scalar type named in the conflicting comments, construct its ordinary +zero/empty value and `ray_typed_null(type)`, then assert `RAY_ATOM_IS_NULL` on +both. The test results, sentinel definitions, and all comments in the section +must describe the same matrix with no exception left implicit. + +## Validation + +Validated. The conflicting comments and `NULL_F32` definition remain. +`ray_typed_null` and `ray_atom_is_null_fn` confirm that F32 uses NaN, BOOL/U8 +use the aux bit, and SYM/STR ordinary empty values are not null. Existing tests +cover numeric sentinels but not the full public matrix named by the finding. + +## Census + +A project-wide search for statements assigning null status to empty SYM/STR +values or denying F32's sentinel found fifteen conflicting statements across +the public header, implementation comments, documentation, and tests. The full +census and ratification requirement are recorded in CF-0003. This finding is +superseded pending triage of that class finding. + +## Objection + + +## Remediation + +Superseded by CF-0003 during RP-0001; no point fix was applied. + + +## Verification diff --git a/audit/findings/F-0005-time-atom-documentation-uses-wrong-unit.md b/audit/findings/F-0005-time-atom-documentation-uses-wrong-unit.md new file mode 100644 index 000000000..8ce7b4f70 --- /dev/null +++ b/audit/findings/F-0005-time-atom-documentation-uses-wrong-unit.md @@ -0,0 +1,80 @@ +--- +id: F-0005 +title: Time atom documentation uses nanoseconds for a millisecond value +severity: major +dimension: api-doc-consistency +unit: + - docs/docs/c-api/core.md + - include/rayforce.h + - test/test_public_api.c +status: fixed +class: null +attempts: 0 +pass: P-01 +updated: 2026-07-22 +--- + +## Evidence + +`docs/docs/c-api/core.md`, section “ray_time” (lines 197–203), specifies: + +> Creates a time atom. The value is nanoseconds since midnight. + +`test/test_public_api.c`, `test_public_vec_get_i64_time` (lines 273–280), uses +values explicitly labelled as wall-clock times: + +```c +int32_t xs[] = { 0, 43200000, 86399000 }; /* 00:00:00.000, 12:00:00.000, 23:59:59.000 */ +``` + +Those values are milliseconds since midnight, not nanoseconds. The same test's +section comment (lines 256–258) and the public null predicate in +`include/rayforce.h` (lines 465–467) also establish that `RAY_TIME` uses a +32-bit `i32` representation; a full day in nanoseconds cannot fit in that +representation. + +## Why this is a defect + +The public constructor documentation scales every time value by the wrong +factor of one million. An embedder following it cannot represent ordinary +times in the declared 32-bit storage and will create incorrect values. + +## How to verify the fix + +Re-read the `ray_time` section and run a public API test constructing noon and +23:59:59.000 with the documented unit. Expected values must match the existing +32-bit executable contract (`43200000` and `86399000` for milliseconds), and +the documentation must name that unit exactly. + +## Validation + +Validated. The API page still says nanoseconds. `src/lang/format.c` converts the +stored `int32_t` value by dividing by 1000, `src/ops/temporal.c` names the unit +as milliseconds, and the public API test uses `43200000` for noon. The runtime +contract is milliseconds. + +## Census + +Pattern: a contract for `RAY_TIME` or `ray_time` names a unit other than +milliseconds since midnight. Searched with +`rg -n -i '(RAY_TIME|time atom|time-of-day).*\b(nanosecond|microsecond|millisecond)|\b(nanosecond|microsecond|millisecond)s? since midnight' +include src docs test README.md examples fuzz bench`. Only +`docs/docs/c-api/core.md:199` says nanoseconds; the type guide, data-type guide, +syntax reference, implementation comments, and tests consistently say +milliseconds. One instance is below threshold. + +## Objection + + +## Remediation + +Changed `docs/docs/c-api/core.md`, section `ray_time`, to specify milliseconds +since midnight. Strengthened `test_public_vec_get_i64_time` in +`test/test_public_api.c` by constructing `ray_time(43200000)` and asserting its +public formatted value is `12:00:00.000`. + +Self-check: the stale “nanoseconds since midnight” text is absent, the focused +public TIME test passed, and `make test` passed 3638/3638 under ASan/UBSan. + + +## Verification diff --git a/audit/findings/F-0006-public-api-test-redeclares-runtime-functions.md b/audit/findings/F-0006-public-api-test-redeclares-runtime-functions.md new file mode 100644 index 000000000..0ec30df4e --- /dev/null +++ b/audit/findings/F-0006-public-api-test-redeclares-runtime-functions.md @@ -0,0 +1,79 @@ +--- +id: F-0006 +title: Public API test redeclares runtime functions instead of testing the header +severity: minor +dimension: api-doc-consistency +unit: + - test/test_public_api.c + - include/rayforce.h +status: superseded-by-class +class: CF-0004 +attempts: 0 +pass: P-01 +updated: 2026-07-22 +--- + +## Evidence + +`test/test_public_api.c` includes the public header at line 27: + +```c +#include +``` + +but then manually supplies the public runtime declarations in the file-scope +setup block (lines 34–40): + +```c +struct ray_runtime_s; +typedef struct ray_runtime_s ray_runtime_t; +extern ray_runtime_t* ray_runtime_create(int argc, char** argv); +extern void ray_runtime_destroy(ray_runtime_t* rt); +``` + +The declarations the test is meant to guard already exist in +`include/rayforce.h` (lines 644–664): + +```c +typedef struct ray_runtime_s ray_runtime_t; +ray_runtime_t* ray_runtime_create(int argc, char** argv); +void ray_runtime_destroy(ray_runtime_t* rt); +``` + +## Why this is a defect + +The “public API” test would continue compiling if those runtime declarations +were accidentally removed from the public header, because the test silently +recreates them. It therefore does not guard the public declaration contract it +appears to exercise. + +## How to verify the fix + +Remove the local runtime type/function redeclarations and compile the public API +test using only `` for those symbols. It must pass. As an adversarial +guard check in a temporary copy, hide `ray_runtime_create` from the public +header and confirm compilation then fails. + +## Validation + +Validated. `test/test_public_api.c` still includes `` and then +redeclares `ray_runtime_t`, `ray_runtime_create`, and `ray_runtime_destroy`. +The same declarations remain in the public header, so the local declarations +still mask a header-removal regression. + +## Census + +The two-stage project search recorded in CF-0004 found sixteen files that +directly include `rayforce.h` and then repeat at least one public runtime type +or function declaration. Sixteen exceeds the threshold, so this finding is +superseded by CF-0004 pending human triage. + +## Objection + + +## Remediation + +Superseded by CF-0004 during RP-0001; no point fix was applied. + + +## Verification diff --git a/audit/findings/F-0007-date-epoch-comment-conflicts-with-api-reference.md b/audit/findings/F-0007-date-epoch-comment-conflicts-with-api-reference.md new file mode 100644 index 000000000..f5db554a6 --- /dev/null +++ b/audit/findings/F-0007-date-epoch-comment-conflicts-with-api-reference.md @@ -0,0 +1,77 @@ +--- +id: F-0007 +title: Public date test labels a different epoch than the API reference +severity: minor +dimension: api-doc-consistency +unit: + - docs/docs/c-api/core.md + - test/test_public_api.c +status: fixed +class: null +attempts: 0 +pass: P-01 +updated: 2026-07-22 +--- + +## Evidence + +`docs/docs/c-api/core.md`, section “ray_date” (lines 189–195), defines the norm: + +> Creates a date atom. The value is an integer representing days since epoch (2000-01-01). + +`test/test_public_api.c`, `test_public_vec_get_i64_date` (lines 260–268), labels +the raw values using a Unix epoch instead: + +```c +int32_t xs[] = { 0, 8766, 19724 }; /* 1970.01.01, 1994.01.01, 2024.01.01 */ +``` + +The two passages are an N13 internal-consistency contradiction: in the API +reference, day zero is 2000-01-01; in the public API test comment, day zero is +1970-01-01. + +## Why this is a defect + +The accessor test's misleading semantic labels can cause future tests and API +examples to encode dates against the wrong epoch, even though the current test +only asserts raw integer round-trips. + +## How to verify the fix + +Search both units for the quoted epoch/date labels and confirm they name one +epoch consistently. Add or run a focused semantic test in which day zero is +formatted or otherwise converted to the documented calendar date, rather than +only round-tripped as an integer. + +## Validation + +Validated. The contradictory 1970 and 2000 labels remain. The calendar helpers +and temporal implementation use 2000-01-01 as the Rayforce epoch, so the API +reference is correct and the public test's inline calendar labels are stale. + +## Census + +Pattern: a `RAY_DATE` raw-value comment or contract labels day zero as 1970 +rather than 2000-01-01. Searched date contracts and semantic tests with +`rg -n -i '(RAY_DATE|date atom|date value|date).*\b(1970|2000-01-01|days since|epoch)|\b1970\.01\.01' +include src docs test README.md examples fuzz bench`. Only +`test/test_public_api.c:264` uses the 1970 label; implementation, API reference, +and semantic tests consistently use 2000-01-01. One instance is below +threshold. + +## Objection + + +## Remediation + +Corrected the calendar labels beside the raw values in +`test_public_vec_get_i64_date` to the 2000-01-01 epoch: day 0 is 2000.01.01, +day 8766 is 2024.01.01, and day 19724 is 2054.01.01. Added a public semantic +check that formats `ray_date(0)` and requires `2000.01.01`, while retaining all +raw accessor assertions. + +Self-check: the stale 1970/1994 labels are absent, the focused public DATE test +passed, and `make test` passed 3638/3638 under ASan/UBSan. + + +## Verification diff --git a/audit/passes/P-01-api-doc-consistency.md b/audit/passes/P-01-api-doc-consistency.md new file mode 100644 index 000000000..6a8f74460 --- /dev/null +++ b/audit/passes/P-01-api-doc-consistency.md @@ -0,0 +1,70 @@ +--- +id: P-01 +dimension: api-doc-consistency +units: + - include/rayforce.h + - docs/docs/c-api/core.md + - test/test_public_api.c +status: done +findings: + - F-0001 + - F-0002 + - F-0003 + - F-0004 + - F-0005 + - F-0006 + - F-0007 +updated: 2026-07-22 +--- + +## Charter + +Compare the public header, published Core C API reference, and public API test +for declaration, ownership, error, and lifecycle consistency. Stop when all +three units are exhausted, 15 findings are filed, or context is exhausted. + +## Coverage + +COVERED: + +- `include/rayforce.h`, lines 1–770: all public declarations and comments were + read attentively, with focused comparison of lifecycle, ownership, errors, + null representation, temporal units, and declarations exercised by the + public API test. +- `docs/docs/c-api/core.md`, lines 1–517: every section, declaration, example, + ownership statement, lifecycle instruction, temporal description, and error + table was compared against the public header and applicable test evidence. +- `test/test_public_api.c`, lines 1–662: all setup/teardown paths, public symbol + guards, accessor cases, runtime lifecycle tests, interrupt/restricted-mode + tests, and error cleanup paths were examined. +- A temporary extraction of the documented lifecycle pattern was compiled with + `cc -std=c17 -Werror -fsyntax-only -Iinclude`; it failed on the two undeclared + heap lifecycle calls, corroborating F-0001. +- A mechanical `ray_*` symbol comparison and literal-anchor search was run + across all three units to check omissions and re-find every filed quote. + +NOT COVERED: + +- Nothing inside the three-unit charter was skipped. +- Implementations outside the charter (including `src/core/runtime.c`, + `src/mem/cow.c`, and `src/vec/atom.c`) were not opened. Runtime behavior was + not inferred from those files; findings rely on contradictions among the + scoped public contracts and executable-test material. +- The full test suite was not run because this pass audits API consistency, not + implementation correctness; each finding supplies its own focused procedure + for remediation self-check and independent verification. + +## Findings + +- F-0001 — Core lifecycle example does not compile against its documented header +- F-0002 — Documentation and public API tests release errors with a no-op helper +- F-0003 — ray_eval_str null-return contract contradicts the public value invariant +- F-0004 — Public null-sentinel contract contradicts its own predicates +- F-0005 — Time atom documentation uses nanoseconds for a millisecond value +- F-0006 — Public API test redeclares runtime functions instead of testing the header +- F-0007 — Public date test labels a different epoch than the API reference + +## Handoff + +No leftover P-01 scope and no split pass. All seven findings are `reported` and +await human triage. P-02 remains the next queued audit pass. diff --git a/audit/plans/RP-0001.md b/audit/plans/RP-0001.md new file mode 100644 index 000000000..57907c6c4 --- /dev/null +++ b/audit/plans/RP-0001.md @@ -0,0 +1,82 @@ +--- +id: RP-0001 +findings: + - F-0001 + - F-0002 + - F-0003 + - F-0004 + - F-0005 + - F-0006 + - F-0007 +status: done +updated: 2026-07-22 +--- + +## Batch validation results + +- F-0001: validated. The documented lifecycle snippet still fails C17/Werror + syntax checking because it calls internal heap functions without their + header; the public runtime lifecycle is declared and tested. +- F-0002: validated. All three cited calls remain and `ray_release` still + returns immediately for an error object. +- F-0003: validated. The public comment still says bare NULL while the + evaluator and focused test use `RAY_NULL_OBJ`. +- F-0004: validated. The null-contract comments still contradict the public + predicate and typed-null implementation. +- F-0005: validated. The C API page says nanoseconds while the runtime, + formatter, and tests use milliseconds. +- F-0006: validated. The public API test still repeats declarations already + supplied by `rayforce.h`. +- F-0007: validated. The public test still labels the raw date values with a + 1970 epoch while the implementation and reference use 2000-01-01. + +## Census results + +- F-0001: one defective standalone lifecycle example. Project-wide review of + examples using `ray_heap_init`/`ray_heap_destroy` found the README and DAG + examples include `mem/heap.h`; the core “Typical lifecycle pattern” does + not. Below threshold; proceed with the point fix. +- F-0002: four public-contract instances. Escalated to CF-0001; F-0002 is + superseded-by-class and the CF returns to human triage. +- F-0003: three comments claim successful null evaluation returns bare NULL. + Escalated to CF-0002; F-0003 is superseded-by-class. +- F-0004: fifteen conflicting null-encoding statements. Escalated to CF-0003; + F-0004 is superseded-by-class and its norm conflict requires ratification. +- F-0005: one incorrect TIME-unit statement; all searched sibling contracts + say milliseconds. Below threshold; proceed with the point fix. +- F-0006: sixteen files include the public header and then redeclare its + runtime API. Escalated to CF-0004; F-0006 is superseded-by-class. +- F-0007: one 1970-epoch label; searched date contracts and semantic tests use + the 2000-01-01 epoch. Below threshold; proceed with the point fix. + +## Fix plan + +- F-0001: replace the internal heap/symbol lifecycle in the canonical snippet + with `ray_runtime_create`/`ray_runtime_destroy`, check creation failure, and + syntax-check the extracted snippet against only `rayforce.h`. Risk: the page + still documents internal lifecycle functions elsewhere; keep that material + explicitly identified as internal rather than silently changing its scope. +- F-0005: change the `ray_time` reference unit from nanoseconds to + milliseconds and strengthen the public accessor test with a constructed noon + atom so the documented unit is executable. Risk: avoid changing TIMESTAMP's + separate nanosecond contract. +- F-0007: correct the test values/calendar labels to the 2000 epoch and add a + semantic day-zero assertion through public formatting. Risk: keep the raw + accessor round-trip coverage intact. + +F-0002, F-0003, F-0004, and F-0006 receive no project edits in this session; +their newly reported CFs must pass triage first. + +## Execution log + +- F-0001: revised `docs/docs/c-api/core.md` to use the public runtime + lifecycle. Extracted snippet passed C17/Werror syntax checking. +- F-0005: corrected the TIME unit in `docs/docs/c-api/core.md` and added a + noon-format assertion in `test/test_public_api.c`. +- F-0007: corrected the date epoch labels and added a day-zero format assertion + in `test/test_public_api.c`. +- F-0002, F-0003, F-0004, F-0006: opened CF-0001 through CF-0004 respectively + and made no project edits for those classes pending triage. +- Batch self-check: `make test` completed with 3638/3638 passing and no + failures; the lifecycle compile probe exited 0; stale TIME/DATE strings were + absent on re-search. diff --git a/audit/plans/RP-0002.md b/audit/plans/RP-0002.md new file mode 100644 index 000000000..257986994 --- /dev/null +++ b/audit/plans/RP-0002.md @@ -0,0 +1,42 @@ +--- +id: RP-0002 +findings: + - CF-0001 +status: done +updated: 2026-07-22 +--- + +## Batch validation results + +- CF-0001: validated. Re-running the recorded census reproduced all four + eligible public-contract instances. The header and implementation confirm + that `ray_release` is a no-op for errors and `ray_error_free` is the owned + error cleanup. + +## Census results + +- CF-0001: the accepted class census is the batch scope. Its recorded search + found four eligible instances across the public C API reference and + public-header conformance test; re-running it reproduced the same four. + +## Fix plan + +- CF-0001: apply the accepted rung-(a) strategy by replacing each eligible + `ray_release(result)` or `ray_release(err)` call with `ray_error_free` in + `docs/docs/c-api/core.md`, `docs/docs/c-api/dag.md`, and + `test/test_public_api.c`. Then re-run the census, execute the public API test + through the project test suite, and rebuild the documentation strictly. + Risk: an error might be referenced by error-trace state, but inspection + confirms the trace is a separate VM-owned list and the next evaluation + clears it independently. + +## Execution log + +- CF-0001: replaced four erroneous `ray_release` cleanup calls with + `ray_error_free` across the two public API reference examples and two + public-header conformance tests. +- Census self-check: the recorded search has zero eligible error-cleanup + instances; its remaining three matches are success paths explicitly guarded + by `!RAY_IS_ERR`. +- Test self-check: `make test` passed 3638/3638 with no skips or failures. +- Documentation self-check: `mkdocs build --strict` completed successfully. diff --git a/audit/plans/RP-0003.md b/audit/plans/RP-0003.md new file mode 100644 index 000000000..65d533391 --- /dev/null +++ b/audit/plans/RP-0003.md @@ -0,0 +1,47 @@ +--- +id: RP-0003 +findings: + - CF-0002 +status: done +updated: 2026-07-22 +--- + +## Batch validation results + +- CF-0002: validated. The two harness comments are found by the recorded + regex; the multiline public-header comment requires the supplementary + exact-literal census recorded in the finding. Runtime-focused tests assert + that successful null evaluation returns `RAY_NULL_OBJ`. + +## Census results + +- CF-0002: the accepted class census is the batch scope. The corrected + supplementary search reproduces all three recorded instances across + `include/rayforce.h`, `test/main.c`, and `test/stress_eval.c`. + +## Fix plan + +- CF-0002: apply the accepted rung-(b) strategy. Name `RAY_NULL_OBJ` as the + successful void/null result in the public `ray_eval_str` contract and both + harness comments. Preserve the harnesses' defensive bare-NULL branches, but + describe bare NULL only as a defensive allocation-failure/no-result case, + never as successful value-null. Then run the focused null-evaluation tests + and require both census procedures to return zero stale instances. +- Norm ratification: the human accepted CF-0002 after its Global strategy + explicitly selected the existing `RAY_NULL_OBJ` public invariant as the + source of truth. That triage decision ratifies this rung-(b) direction; the + plan introduces no different norm choice. +- Risk: comments must not overstate the defensive bare-NULL paths as ordinary + successful evaluation results or change executable harness behavior. + +## Execution log + +- CF-0002: aligned the public header and both harness comments with the + `RAY_NULL_OBJ` successful-null contract while preserving defensive bare-NULL + branches unchanged. +- Census self-check: the supplementary stale-literal search returned zero. + The original overbroad regex has one documented corrected-comment exception + in `test/main.c` because it matches any `ray_eval_str` comment containing + “void results.” +- Focused self-check: `lang/eval/println` and `lang/eval/null_keyword` each + passed 1/1 with no skips or failures. diff --git a/audit/plans/RP-0004.md b/audit/plans/RP-0004.md new file mode 100644 index 000000000..fef405b4e --- /dev/null +++ b/audit/plans/RP-0004.md @@ -0,0 +1,65 @@ +--- +id: RP-0004 +findings: + - CF-0003 +status: done +updated: 2026-07-22 +--- + +## Batch validation results + +- CF-0003: validated. The fifteen cited conflicting statements remain. + Implementation and focused tests expose a consistent current behavior, but + public and internal normative comments contradict it. + +## Census results + +- CF-0003: the accepted class census is the batch scope. Re-running the + recorded candidate search and manually classifying results reproduced all + fifteen cited conflicts plus correct counterexamples used as classification + references. + +## Fix plan + +- Mandatory norm-ratification gate: stop before project edits and ask the + human norm owner to select one cross-surface nullability matrix. +- Recommended matrix (preserve executable behavior): + - F32/F64 use NaN null sentinels for atoms and vectors. + - Integer and temporal types use their width-correct `NULL_*` sentinels for + atoms and vectors; GUID uses its all-zero sentinel. + - BOOL/U8 typed-null atoms use the auxiliary bit, while BOOL/U8 vectors are + non-nullable. + - SYM/STR atoms and vectors have no null distinct from their ordinary empty + values; missing SYM/STR inputs collapse to empty without + `RAY_ATTR_HAS_NULLS`. +- Alternative matrix: make empty SYM/STR genuinely null. This requires a + distinct representation and behavioral redesign so ordinary empty values + remain expressible; it cannot be implemented as comment cleanup alone. +- If the recommended matrix is ratified, write it into the public API contract, + correct all fifteen census comments/docs/tests without changing executable + behavior, add a focused public matrix test covering ordinary zero/empty and + `ray_typed_null` for every named scalar type, then run the focused null, + vector, CSV, dictionary, formatting, string, pivot, and stress tests and + reclassify the census to zero conflicts. +- If the alternative is selected, replace this plan with an implementation + plan for the new representation before touching the corpus. +- Risk: changing SYM/STR representation affects ingestion, storage, grouping, + dictionary lookup, formatting, and persistence. Comment-only normalization + is safe only after the preserve-behavior matrix is explicitly ratified. + +## Execution log + +- 2026-07-22: the human norm owner explicitly ratified the recommended + preserve-behavior matrix. The mandatory gate is satisfied; execution began. +- CF-0003: published the matrix in `include/rayforce.h`, normalized all + fifteen censused statements, and added `public/nullability_matrix` without + changing executable representation. +- Census self-check: zero conflicting statements; the seven remaining regex + matches are correct counterexamples saying empty SYM is not null. +- Build self-check: ASan/UBSan debug build succeeded under `-Werror`. +- Test self-check: 3,639/3,639 passed with zero skips and zero failures. +- Documentation self-check: `mkdocs build --strict` succeeded. +- Supplemental review found a separate behavior-level candidate around + approximate distinct handling of empty SYM/STR values. It is outside the + accepted fifteen-statement comment census and was not modified here; a + future audit pass can evaluate it against the newly ratified contract. diff --git a/audit/templates/class-finding.md b/audit/templates/class-finding.md new file mode 100644 index 000000000..7d698a65f --- /dev/null +++ b/audit/templates/class-finding.md @@ -0,0 +1,53 @@ +--- +id: CF-XXXX +title: # short title of the systemic defect (operator's working language) +severity: # highest severity among members +dimension: # dimension key from AUDIT.md +unit: [] # YAML list of every unit the class spans (from the census); canonical field per §2 +status: reported +class: null # a CF is itself the class — it is not absorbed by another class +members: [] # F-XXXX ids absorbed by this class (they get status superseded-by-class) +attempts: 0 +pass: P-XX # the pass whose finding triggered the census escalation (— if surfaced purely in remediation) +created-by: RP-XXXX # the remediation session whose census escalated +updated: YYYY-MM-DD +--- + +## Pattern + + +## Census + + +## Validation + + +## Root cause + + +## Global strategy + + +## Remediation + + +## Verification + diff --git a/audit/templates/finding.md b/audit/templates/finding.md new file mode 100644 index 000000000..3e72dccae --- /dev/null +++ b/audit/templates/finding.md @@ -0,0 +1,46 @@ +--- +id: F-XXXX +title: # short title of the defect (operator's working language) +severity: # critical | major | minor | info +dimension: # one dimension key from AUDIT.md +unit: # material unit; YAML list if the finding spans units +status: reported +class: null # CF-XXXX once absorbed by a class finding +attempts: 0 # the Remediator increments this when re-taking a reopened finding +pass: P-XX # the audit pass that produced this finding +updated: YYYY-MM-DD +--- + +## Evidence + + +## Why this is a defect + + +## How to verify the fix + + +## Validation + + +## Objection + + +## Remediation + + +## Verification + diff --git a/audit/templates/pass.md b/audit/templates/pass.md new file mode 100644 index 000000000..20ed97e0e --- /dev/null +++ b/audit/templates/pass.md @@ -0,0 +1,28 @@ +--- +id: P-XX +dimension: # one dimension key from AUDIT.md +units: [] # material units this pass covers +status: queued # queued | done | split +findings: [] # F-XXXX ids produced +updated: YYYY-MM-DD +--- + +## Charter + + +## Coverage + + +## Findings + + +## Handoff + diff --git a/audit/templates/plan.md b/audit/templates/plan.md new file mode 100644 index 000000000..24118c66e --- /dev/null +++ b/audit/templates/plan.md @@ -0,0 +1,36 @@ +--- +id: RP-XXXX +findings: [] # accepted F-XXXX and/or CF-XXXX ids in this batch (≤ remediation_batch_size); + # an accepted CF is a batch of its own (§8) +status: open # open | done +updated: YYYY-MM-DD +--- + +## Batch validation results + + +## Census results + + +## Fix plan + + +## Execution log + diff --git a/docs/assets/ai-assist-widget.css b/docs/assets/ai-assist-widget.css index b985ed161..957dc522f 100644 --- a/docs/assets/ai-assist-widget.css +++ b/docs/assets/ai-assist-widget.css @@ -325,3 +325,188 @@ .rfai-messages { padding: 12px; } .rfai-msg { max-width: 100%; } } + +/* 2026 editorial shell: keep assistance present without obscuring the page. */ +.rfai-launcher { + right: max(22px, env(safe-area-inset-right)); + bottom: max(22px, env(safe-area-inset-bottom)); + min-width: 112px; + min-height: 48px; + gap: 8px; + padding: 0 15px 0 10px; + border-radius: 0; + background: rgba(8, 11, 16, .9); + border-color: rgba(233, 160, 51, .55); + color: var(--rfai-primary-lighter); + box-shadow: 0 16px 54px rgba(0, 0, 0, .42); + backdrop-filter: blur(14px); + font-family: var(--rfai-mono); + font-size: .72rem; + letter-spacing: .02em; +} +.rfai-launcher:hover { + border-color: var(--rfai-primary); + background: var(--rfai-primary); + color: #171109; + box-shadow: 0 18px 60px rgba(0, 0, 0, .48); +} +.rfai-launcher-icon { width: 28px; height: 28px; border-radius: 0; background: rgba(233,160,51,.12); } +.rfai-launcher-icon svg { width: 18px; height: 18px; filter: none; } +.rfai-dialog { border-radius: 0; border-color: rgba(233,160,51,.28); } +.rfai-icon-button,.rfai-msg,.rfai-question,.rfai-send,.rfai-suggestions button,.rfai-table-wrap,.rfai-msg .rfai-content pre { border-radius: 0; } + +@media (max-width: 768px) { + .rfai-launcher { + right: max(12px, env(safe-area-inset-right)); + bottom: max(12px, env(safe-area-inset-bottom)); + min-width: 46px; + width: 46px; + min-height: 46px; + padding: 0; + } + .rfai-launcher > span:last-child { display: none; } + .rfai-launcher-icon { background: transparent; } + .rfai-dialog { width: calc(100vw - 16px); height: calc(100vh - 22px); border-radius: 0; } +} + +/* Unified 2026 brand surface: the AI panel mirrors the landing workbench. */ +.rfai-launcher { + min-width: 124px; + min-height: 50px; + padding: 0 16px 0 10px; + border-color: rgba(233,160,51,.5); + background: rgba(8,11,16,.94); + color: #f3c46b; + font-family: var(--rfai-mono); + font-size: 11px; + font-weight: 600; + letter-spacing: .08em; + text-transform: uppercase; +} +.rfai-launcher:hover { + border-color: #e9a033; + background: #e9a033; + color: #171109; +} +.rfai-launcher-icon { border: 1px solid rgba(233,160,51,.28); box-shadow: none; } + +.rfai-dialog { + width: min(860px, calc(100vw - 48px)); + height: min(650px, calc(100vh - 72px)); + background: #080b10; + border-color: rgba(255,255,255,.18); + box-shadow: 0 32px 120px rgba(0,0,0,.72); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); +} +.rfai-header { + min-height: 76px; + padding: 0 20px; + border-bottom-color: rgba(255,255,255,.11); + background: #080b10; +} +.rfai-title-row { gap: 18px; } +.rfai-logo { + width: 122px; + padding-right: 18px; + border-right: 1px solid rgba(255,255,255,.12); +} +.rfai-header h2 { + font-size: 24px; + letter-spacing: -.02em; + text-transform: uppercase; +} +.rfai-header p { + margin-top: 4px; + color: #667583; + font-family: var(--rfai-mono); + font-size: 9px; + letter-spacing: .08em; + text-transform: uppercase; +} +.rfai-icon-button { + width: 40px; + height: 40px; + border-color: rgba(255,255,255,.14); + background: transparent; +} +.rfai-icon-button:hover { border-color: #e9a033; background: rgba(233,160,51,.08); } + +.rfai-messages { + gap: 14px; + padding: 22px; + background-color: #0b1017; + background-image: + linear-gradient(rgba(255,255,255,.022) 1px, transparent 1px), + linear-gradient(90deg, rgba(255,255,255,.022) 1px, transparent 1px); + background-size: 56px 56px; +} +.rfai-msg { + padding: 13px 15px; + border-color: rgba(255,255,255,.11); + background: #101720; + box-shadow: none; +} +.rfai-msg .rfai-content p, +.rfai-msg .rfai-content ul { color: #9aa7b3; font-family: var(--rfai-font); font-size: 14px; } +.rfai-msg-assistant.rfai-welcome { + border-left: 3px solid #e9a033; + background: rgba(17,25,35,.92); +} +.rfai-msg-user { + border-color: rgba(233,160,51,.38); + background: rgba(233,160,51,.1); +} +.rfai-msg .rfai-content h3, +.rfai-msg .rfai-content h4, +.rfai-msg .rfai-content h5 { + letter-spacing: -.01em; + text-transform: uppercase; +} +.rfai-msg .rfai-content code { border-radius: 0; } +.rfai-suggestions { gap: 8px; margin-top: 0; } +.rfai-suggestions button { + min-height: 34px; + padding: 7px 11px; + border-color: rgba(233,160,51,.22); + background: rgba(8,11,16,.8); + color: #9aa7b3; + font-family: var(--rfai-mono); + font-size: 10px; + font-weight: 600; + letter-spacing: .06em; + text-transform: uppercase; +} +.rfai-suggestions button:hover { color: #171109; border-color: #e9a033; background: #e9a033; } +.rfai-form { + gap: 10px; + padding: 16px; + border-top-color: rgba(255,255,255,.11); + background: #080b10; +} +.rfai-question { + min-height: 54px; + border-color: rgba(255,255,255,.14); + background: #0b1017; + color: #f8fafc; + font-family: var(--rfai-font); + font-size: 14px; +} +.rfai-question:focus { border-color: #e9a033; box-shadow: none; } +.rfai-send { + width: 56px; + min-height: 54px; + border-color: #e9a033; + background: #e9a033; + color: #171109; + box-shadow: none; +} +.rfai-send:hover { background: #f3c46b; } + +@media (max-width: 768px) { + .rfai-dialog { width: calc(100vw - 12px); height: calc(100svh - 12px); } + .rfai-header { min-height: 68px; padding: 0 14px; } + .rfai-logo { width: 102px; padding-right: 12px; } + .rfai-header h2 { font-size: 20px; } + .rfai-messages { padding: 14px; } +} diff --git a/docs/assets/extra.css b/docs/assets/extra.css index 6d73c055a..73c59ada1 100644 --- a/docs/assets/extra.css +++ b/docs/assets/extra.css @@ -10,14 +10,14 @@ font-family: 'Inter'; font-style: normal; font-weight: 400; - font-display: swap; + font-display: block; src: url('/assets/fonts/inter-400.woff2') format('woff2'); } @font-face { font-family: 'Inter'; font-style: normal; font-weight: 600; - font-display: swap; + font-display: block; src: url('/assets/fonts/inter-600.woff2') format('woff2'); } @font-face { @@ -38,21 +38,21 @@ font-family: 'Oswald'; font-style: normal; font-weight: 700; - font-display: swap; + font-display: block; src: url('/assets/fonts/oswald-700.woff2') format('woff2'); } @font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400; - font-display: swap; + font-display: block; src: url('/assets/fonts/jbmono-400.woff2') format('woff2'); } @font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 600; - font-display: swap; + font-display: block; src: url('/assets/fonts/jbmono-600.woff2') format('woff2'); } @@ -94,6 +94,7 @@ share the same tokens as the marketing site without hand-editing colour values across the file. */ :root { + --rf-banner-h: 34px; --bg: #0a0e13; --bg-section: #0d1219; --bg-card: #111820; @@ -172,20 +173,6 @@ --md-typeset-a-color: #c4821a; } -/* ── Heading permalinks: hide the ¶ but keep them clickable on hover. - `permalink: true` in mkdocs.yml emits a - beside every heading — useful for "copy link to section" but visually - noisy. Hide by default, fade in only when the heading is hovered. */ -.md-typeset .headerlink { - opacity: 0; - margin-left: 0.4em; - color: var(--accent); - transition: opacity .15s; -} -.md-typeset :is(h1, h2, h3, h4, h5, h6):hover .headerlink, -.md-typeset .headerlink:focus { opacity: 0.7; } -.md-typeset .headerlink:hover { opacity: 1; } - /* ── Headings: Oswald, marketing-style ────────────────────────── */ .md-typeset h1, @@ -509,36 +496,845 @@ html.landing-page body .footer.landing-footer { display: block; } .md-banner__inner wrappers so .rf-banner is the only styled box. In normal flow at the very top of every page: on docs it scrolls away above the sticky header (Material-native), on the landing/about pages main.js offsets the - floating pill nav below it via --rf-banner-h. */ + fixed site nav below it via --rf-banner-h. */ .md-banner, .md-banner__inner { display: contents; } .rf-banner { - display: flex; align-items: center; justify-content: center; gap: 10px; - position: relative; padding: 8px 46px; min-height: 38px; - background: linear-gradient(90deg, var(--navy, #0e1b24), #122430); - color: var(--text, #f1f5f9); - border-bottom: 1px solid var(--border, rgba(233, 160, 51, 0.18)); - font-size: .82rem; text-align: center; + display: block; position: relative; min-height: 34px; padding: 0; + background: #0d292b; color: #bad0d0; + border-bottom: 1px solid rgba(94, 234, 212, .22); + font-family: 'JetBrains Mono', ui-monospace, monospace; + font-size: .63rem; letter-spacing: .035em; } .rf-banner[hidden] { display: none; } +.rf-banner-inner { + display: flex; align-items: center; gap: 14px; + width: min(calc(100% - 80px), 1380px); min-height: 34px; margin: 0 auto; +} +.rf-banner-label { + align-self: stretch; display: inline-flex; align-items: center; padding: 0 12px; + background: #5eead4; color: #071516; font-weight: 700; + letter-spacing: .1em; text-transform: uppercase; +} .rf-banner-msg { - display: inline-flex; align-items: center; gap: 8px; - flex-wrap: wrap; justify-content: center; + overflow: hidden; color: #bad0d0; text-overflow: ellipsis; white-space: nowrap; } -.rf-banner-spark { color: var(--primary, #e9a033); } .rf-banner-link { - display: inline-flex; align-items: center; gap: 5px; - color: var(--primary-light, #f0b954); font-weight: 700; - text-decoration: none; border-bottom: 1px solid transparent; + display: inline-flex; align-items: center; gap: 7px; margin-left: auto; + width: 140px; justify-content: flex-end; + color: #f3c46b; font-weight: 700; text-transform: uppercase; + text-decoration: none; } .rf-banner-link:hover { - color: var(--primary-lighter, #f5d080); border-bottom-color: currentColor; + color: #f8fafc; } .rf-banner-close { - position: absolute; right: 12px; top: 50%; transform: translateY(-50%); display: inline-flex; padding: 4px; border: 0; background: none; - color: var(--text-muted, #7b8fa0); cursor: pointer; border-radius: 6px; + color: #526170; cursor: pointer; border-radius: 0; line-height: 0; transition: color .2s, background .2s; } .rf-banner-close:hover { color: var(--text, #f1f5f9); background: rgba(255, 255, 255, 0.08); } +@media screen and (max-width: 600px) { + .rf-banner-inner { width: calc(100% - 24px); gap: 9px; } + .rf-banner-label { padding-inline: 8px; } + .rf-banner-msg { flex: 1; } + .rf-banner-link { width: auto; margin-left: 0; } + .rf-banner-link [data-download-label] { display: none; } +} + +/* 2026 documentation shell — the same sharp, technical visual language as home. */ +[data-md-color-scheme="slate"] { + --md-default-bg-color: #080b10; + --md-default-bg-color--light: #0c1118; + --md-default-bg-color--lighter: #111923; + --md-default-bg-color--lightest: #17212c; + --md-code-bg-color: #0b1017; +} + +[data-md-color-scheme="slate"] body { + background-color: #080b10; + background-image: + linear-gradient(rgba(255,255,255,.014) 1px, transparent 1px), + linear-gradient(90deg, rgba(255,255,255,.014) 1px, transparent 1px); + background-size: 72px 72px; +} + +.md-header { + height: 70px; + background: rgba(8, 11, 16, .94); + border-bottom: 1px solid rgba(255,255,255,.09); + box-shadow: none; + backdrop-filter: blur(16px); +} +.md-header__inner { height: 70px; max-width: 1240px; } +.md-header .md-logo img, +.md-header .md-logo svg { height: 40px; max-height: 40px; } +.md-search__form { border: 1px solid rgba(255,255,255,.08); border-radius: 0; background: #0b1017; } +.md-search__input { border-radius: 0; } + +.md-main__inner { max-width: 1240px; padding-top: 2.6rem; } +.md-content__inner { max-width: 780px; } +.md-sidebar { color: #9aa7b3; } +.md-sidebar__scrollwrap { scrollbar-width: thin; } +.md-nav__title { color: #7f8f9d; letter-spacing: .1em; } +.md-nav__link { line-height: 1.35; } +.md-nav__link:hover { color: #f3c46b; } +.md-nav__link--active { + color: #f3c46b; + padding-left: .65rem; + border-left: 2px solid #e9a033; +} + +.md-typeset h1 { + margin-bottom: 2.2rem; + color: #f8fafc; + font-size: clamp(2.8rem, 5vw, 4.6rem); + line-height: .96; + letter-spacing: -.025em; +} +.md-typeset h2 { + margin-top: 3.4rem; + padding-bottom: .55rem; + border-bottom-color: rgba(233,160,51,.22); + font-size: 2rem; +} +.md-typeset h3 { color: #f3c46b; } +.md-typeset p, +.md-typeset li { line-height: 1.7; } + +.md-typeset .md-button { + min-height: 48px; + padding: 12px 22px; + border-radius: 0; + font-family: 'JetBrains Mono', monospace; + font-size: .72rem; +} +.md-typeset .md-button--primary { + color: #171109; + border: 1px solid #e9a033; + background: #e9a033; + box-shadow: 0 12px 36px rgba(233,160,51,.16); +} +.md-typeset .md-button--primary:hover { + color: #171109; + background: #f3c46b; + box-shadow: 0 16px 44px rgba(233,160,51,.22); +} +.md-typeset code { border-radius: 0; } +.md-typeset pre > code, +.md-typeset .highlight, +.md-typeset table:not([class]), +.md-typeset .admonition, +.md-typeset details { border-radius: 0; } +.md-typeset pre > code { border-color: rgba(255,255,255,.1); } +.md-typeset table:not([class]) { border-color: rgba(255,255,255,.1); } +.md-footer { border-top-color: rgba(255,255,255,.1); } + +[data-md-color-scheme="slate"] .md-search__output { border-radius: 0; } +.rf-banner { background: #0d292b; border-bottom-color: rgba(94,234,212,.22); } + +@media screen and (max-width: 76.234375em) { + .md-main__inner { padding-inline: 1rem; } +} + +/* Unified site navigation for documentation pages. */ +.md-header { + height: 76px; + background: rgba(8,11,16,.96); + border-bottom: 1px solid rgba(255,255,255,.11); +} +.md-header__inner { + width: min(calc(100% - 80px), 1380px); + max-width: 1380px; + height: 76px; + margin-inline: auto; + padding: 0; +} +.md-header .md-logo img, +.md-header .md-logo svg { + width: 128px; + height: auto; + max-height: 44px; +} +.docs-nav-links { + display: flex; + align-items: center; + gap: 3px; +} +.docs-nav-links a { + padding: 10px 15px; + color: #b5c0ca; + font-family: 'Inter', 'Inter Fallback', sans-serif; + font-size: 14px; + font-weight: 600; + line-height: 1; + text-decoration: none; + transition: color .2s, background .2s; +} +.docs-nav-links a:hover, +.docs-nav-links a.active { + color: #f8fafc; + background: rgba(255,255,255,.05); +} +.docs-nav-cloud { display: inline-flex; align-items: center; gap: 7px; } +.docs-nav-cloud i { + width: 6px; + height: 6px; + border-radius: 50%; + background: #e9a033; + box-shadow: 0 0 12px #e9a033; +} +.md-header__spacer { flex: 1 1 26px; } +.md-search { margin-left: 10px; } +.md-search__form { width: 184px; } +.docs-nav-github { + min-width: 64px; + min-height: 42px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + margin-left: 12px; + padding: 8px 12px; + border: 1px solid rgba(255,255,255,.2); + color: #f8fafc; + font: 600 12px 'JetBrains Mono', monospace; + text-decoration: none; +} +.docs-nav-github:hover { border-color: #e9a033; background: rgba(233,160,51,.07); } +.docs-nav-github svg { width: 17px; height: 17px; fill: currentColor; } + +/* Docs typography follows the same editorial hierarchy as the home sections. */ +.md-typeset h1, +.md-typeset h2, +.md-typeset h3, +.md-typeset h4 { + font-family: 'Oswald', 'Oswald Fallback', sans-serif; + text-transform: uppercase; +} +.md-typeset h1 { letter-spacing: -.035em; } +.md-typeset h2 { letter-spacing: -.02em; } +.md-nav__title { + font-family: 'JetBrains Mono', monospace; + font-size: .62rem; + letter-spacing: .11em; +} +.md-nav__link { font-family: 'Inter', 'Inter Fallback', sans-serif; } + +@media screen and (max-width: 76.234375em) { + .md-header, + .md-header__inner { height: 70px; } + .md-header__inner { width: calc(100% - 32px); } + .docs-nav-links, + .docs-nav-github { display: none; } + .md-header__spacer { flex: 1; } + .md-search { margin-left: 0; } +} + +/* Editorial docs shell — the documentation is a technical section of the + marketing site, not a separate Material-themed product. */ +html:not(.landing-page) .md-main { + position: relative; + isolation: isolate; + background: + radial-gradient(circle at 76% 8%, rgba(233,160,51,.07), transparent 28%), + linear-gradient(rgba(255,255,255,.018) 1px, transparent 1px), + linear-gradient(90deg, rgba(255,255,255,.018) 1px, transparent 1px), + #080b10; + background-size: auto, 72px 72px, 72px 72px, auto; +} +html:not(.landing-page) .md-main::before { + content: ''; + position: absolute; + z-index: -1; + inset: 0 0 auto 38%; + height: 760px; + background: url('/assets/rayforce-engine-hero.webp') top right / cover no-repeat; + opacity: .075; + filter: saturate(.8); + mask-image: linear-gradient(to bottom, #000, transparent 82%); + pointer-events: none; +} +html:not(.landing-page) .md-main__inner { + width: min(calc(100% - 80px), 1380px); + max-width: 1380px; + margin-inline: auto; + padding-top: 64px; + padding-bottom: 100px; +} +html:not(.landing-page) .md-content { + position: relative; + min-width: 0; + padding-inline: clamp(38px, 4vw, 68px); + border-inline: 1px solid rgba(255,255,255,.08); + background: rgba(8,11,16,.6); + backdrop-filter: blur(2px); +} +html:not(.landing-page) .md-content__inner { + max-width: 860px; + margin-inline: auto; + padding-top: 0; + padding-bottom: 5rem; +} +html:not(.landing-page) .md-content__inner::before { display: none; } +html:not(.landing-page) .md-typeset h1 { + margin: 0 0 36px; + color: #f8fafc; + font-size: clamp(3.6rem, 5vw, 5.8rem); + line-height: .9; + letter-spacing: -.045em; +} +html:not(.landing-page) .md-typeset h1::before { + content: 'RAYFORCE / DOCUMENTATION'; + display: block; + margin-bottom: 26px; + padding-left: 46px; + background: linear-gradient(#e9a033,#e9a033) left center / 32px 1px no-repeat; + color: #f3c46b; + font: 600 10px/1.2 'JetBrains Mono', monospace; + letter-spacing: .13em; +} +html:not(.landing-page) .md-typeset h1 + p { + color: #c1cad3; + font-size: 1.02rem; + line-height: 1.75; +} +html:not(.landing-page) .md-typeset h2 { + margin-top: 4.2rem; + padding-bottom: .7rem; + border-bottom: 1px solid rgba(233,160,51,.24); + font-size: clamp(2.25rem, 3.2vw, 3.5rem); + line-height: 1; +} +html:not(.landing-page) .md-typeset h3 { + margin-top: 2.5rem; + font-size: 1.6rem; + letter-spacing: -.01em; +} +html:not(.landing-page) .md-typeset p, +html:not(.landing-page) .md-typeset li { color: #aab5bf; } +html:not(.landing-page) .md-typeset strong { color: #edf2f6; } +html:not(.landing-page) .md-sidebar { + color: #8f9daa; +} +html:not(.landing-page) .md-sidebar__inner { + padding: 18px 18px 24px; + border-top: 2px solid rgba(233,160,51,.72); + border-right: 1px solid rgba(255,255,255,.08); + border-bottom: 1px solid rgba(255,255,255,.08); + border-left: 1px solid rgba(255,255,255,.08); + background: rgba(11,16,23,.78); +} +html:not(.landing-page) .md-sidebar--secondary .md-sidebar__inner { + border-top-color: rgba(94,234,212,.55); +} +html:not(.landing-page) .md-nav__title { + margin-bottom: 12px; + color: #e9a033; +} +html:not(.landing-page) .md-sidebar--secondary .md-nav__title { color: #5eead4; } +html:not(.landing-page) .md-nav__link { + padding-block: 3px; + font-size: .76rem; +} +html:not(.landing-page) .md-nav__link--active { + padding-left: 10px; + border-left: 2px solid #e9a033; + color: #f3c46b; +} +html:not(.landing-page) .md-typeset pre > code, +html:not(.landing-page) .md-typeset table:not([class]), +html:not(.landing-page) .md-typeset .admonition, +html:not(.landing-page) .md-typeset details { + border-color: rgba(255,255,255,.12); + background-color: rgba(11,16,23,.88); +} +html:not(.landing-page) .md-footer { + background: #06090d; + border-top: 1px solid rgba(255,255,255,.1); +} + +@media screen and (max-width: 76.234375em) { + html:not(.landing-page) .md-main__inner { + width: calc(100% - 32px); + padding-top: 42px; + } + html:not(.landing-page) .md-content { + padding-inline: clamp(18px, 5vw, 48px); + border-inline: 0; + } + html:not(.landing-page) .md-typeset h1 { font-size: clamp(3rem, 12vw, 4.6rem); } +} + +@media screen and (max-width: 30em) { + html:not(.landing-page) .md-typeset .md-button { + display: table; + width: fit-content; + max-width: 100%; + margin: 0 0 10px; + } +} + +/* Documentation surface v2 — use the marketing site's actual composition, + not Material's scaled three-panel application shell. */ +html:not(.landing-page) { font-size: 16px; } +html:not(.landing-page) body { + font-size: 1rem; + line-height: 1.5; + background: #080b10; +} + +/* Exact marketing navigation, with docs-only search kept as an icon overlay. */ +html:not(.landing-page) .docs-site-nav { + position: fixed; + z-index: 1000; + top: var(--rf-banner-h, 0px); + left: 0; + width: 100%; + height: 76px; + color: #f8fafc; + background: rgba(8,11,16,.82); + border-bottom: 1px solid rgba(255,255,255,.11); + box-shadow: none; + backdrop-filter: blur(18px); + -webkit-backdrop-filter: blur(18px); + transition: background .25s, border-color .25s; +} +html:not(.landing-page) .docs-site-nav.nav-scrolled { + background: rgba(8,11,16,.97); + border-color: rgba(233,160,51,.24); +} +html:not(.landing-page) .docs-site-nav .nav-inner { + width: min(calc(100% - 80px), 1380px); + max-width: 1380px; + height: 100%; + display: flex; + align-items: center; + margin-inline: auto; + padding: 0; +} +html:not(.landing-page) .docs-site-nav .nav-brand { + display: flex; + align-items: center; + flex: 0 0 auto; + margin: 0 auto 0 0; + padding: 0; +} +html:not(.landing-page) .docs-site-nav .nav-brand img, +html:not(.landing-page) .docs-site-nav .nav-brand svg { + display: block; + width: 128px; + height: auto; + max-height: 44px; +} +html:not(.landing-page) .docs-site-nav .nav-links { + display: flex; + align-items: center; + gap: 3px; +} +html:not(.landing-page) .docs-site-nav .nav-links > a { + padding: 10px 15px; + color: #b5c0ca; + font: 600 .88rem/1 Inter, 'Inter Fallback', sans-serif; + text-decoration: none; + transition: color .2s, background .2s; +} +html:not(.landing-page) .docs-site-nav .nav-links > a:hover, +html:not(.landing-page) .docs-site-nav .nav-links > a.active { + color: #f8fafc; + background: rgba(255,255,255,.05); +} +html:not(.landing-page) .docs-site-nav .nav-cloud { + display: inline-flex; + align-items: center; + gap: 7px; +} +html:not(.landing-page) .docs-site-nav .nav-cloud i { + width: 6px; + height: 6px; + border-radius: 50%; + background: #e9a033; + box-shadow: 0 0 12px #e9a033; +} +html:not(.landing-page) .docs-site-nav .nav-playground { + display: inline-flex; + align-items: center; + gap: 6px; + color: #f3c46b; +} +html:not(.landing-page) .docs-site-nav .nav-playground svg { + width: 12px; + height: 12px; + fill: currentColor; +} +html:not(.landing-page) .docs-site-nav .nav-github { + width: 90px; + min-width: 64px; + flex: 0 0 90px; + min-height: 42px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + margin-left: 10px; + padding: 9px 12px; + border: 1px solid rgba(255,255,255,.2); + color: #f8fafc; + font: 600 .76rem/1 'JetBrains Mono', monospace; +} +html:not(.landing-page) .docs-site-nav .nav-github:hover { + border-color: #e9a033; + background: rgba(233,160,51,.07); +} +html:not(.landing-page) .docs-site-nav .nav-github svg { + width: 16px; + height: 16px; + fill: currentColor; +} +html:not(.landing-page) .docs-site-nav .nav-toggle { display: none; } +html:not(.landing-page) .docs-search-trigger { + width: 42px; + height: 42px; + display: inline-flex; + align-items: center; + justify-content: center; + margin-left: 10px; + border: 1px solid rgba(255,255,255,.2); + color: #b5c0ca; + cursor: pointer; + transition: color .2s, border-color .2s, background .2s; +} +html:not(.landing-page) .docs-search-trigger:hover { + color: #f8fafc; + border-color: #e9a033; + background: rgba(233,160,51,.07); +} +html:not(.landing-page) .docs-search-trigger > span, +html:not(.landing-page) .docs-browse-trigger { display: none; } +html:not(.landing-page) .docs-site-nav .md-search { + flex: 0 0 0; + width: 0; + margin: 0; +} +html:not(.landing-page) .docs-site-nav .md-search__inner { + width: 0; + overflow: hidden; +} +html:not(.landing-page) .docs-site-nav .md-search__form { width: 100%; } + +/* Full-bleed technical canvas: navigation + article, with the third Material + column removed. Empty space is intentional and carries the engine artwork. */ +html:not(.landing-page) .md-container { padding-top: 76px; } +html:not(.landing-page) .md-main { + position: relative; + isolation: isolate; + min-height: calc(100svh - 76px); + overflow: clip; + background: + radial-gradient(circle at 78% 10%, rgba(233,160,51,.07), transparent 29%), + linear-gradient(rgba(255,255,255,.02) 1px, transparent 1px), + linear-gradient(90deg, rgba(255,255,255,.02) 1px, transparent 1px), + #080b10; + background-size: auto, 76px 76px, 76px 76px, auto; +} +html:not(.landing-page) .md-main::before { + content: ''; + position: absolute; + z-index: -1; + top: 0; + right: 0; + width: 58%; + height: 720px; + background: url('/assets/rayforce-engine-hero.webp') top right / cover no-repeat; + opacity: .11; + filter: saturate(.8); + -webkit-mask-image: linear-gradient(90deg, transparent, #000 25%, #000 70%, transparent); + mask-image: linear-gradient(90deg, transparent, #000 25%, #000 70%, transparent); + pointer-events: none; +} +html:not(.landing-page) .md-main__inner { + width: min(calc(100% - 80px), 1380px); + max-width: 1380px; + min-height: calc(100svh - 110px); + display: grid; + grid-template-columns: 240px minmax(0, 850px) minmax(80px, 1fr); + column-gap: clamp(42px, 5vw, 76px); + align-items: start; + margin: 0 auto; + padding: 72px 0 120px; +} +html:not(.landing-page) .md-sidebar--primary { + grid-column: 1; + width: auto; +} +html:not(.landing-page) .md-sidebar--secondary { display: none; } +html:not(.landing-page) .md-content { + grid-column: 2; + min-width: 0; + padding: 0; + border: 0; + background: transparent; + backdrop-filter: none; +} +html:not(.landing-page) .md-content__inner { + max-width: none; + margin: 0; + padding: 0 0 5rem; +} +html:not(.landing-page) .md-content__inner::before { display: none; } + +/* Navigation is a quiet index rail, not a boxed dashboard panel. */ +html:not(.landing-page) .md-sidebar--primary .md-sidebar__inner { + padding: 24px 30px 30px 0; + border: 0; + border-top: 1px solid rgba(233,160,51,.55); + border-right: 1px solid rgba(255,255,255,.11); + background: transparent; +} +html:not(.landing-page) .md-nav__title { + margin: 0 0 18px; + padding: 0; + color: #e9a033; + font: 600 .64rem/1.3 'JetBrains Mono', monospace; + letter-spacing: .13em; +} +html:not(.landing-page) .md-nav__link { + padding-block: 4px; + color: #8997a4; + font: 500 .8rem/1.4 Inter, 'Inter Fallback', sans-serif; +} +html:not(.landing-page) .md-nav__link:hover { color: #f8fafc; } +html:not(.landing-page) .md-nav__link--active { + padding-left: 11px; + border-left: 2px solid #e9a033; + color: #f3c46b; + font-weight: 600; +} + +/* Same editorial scale as the main site's copy—not Material's wide-screen + root scaling and not a second marketing hero inside the docs. */ +html:not(.landing-page) .md-typeset { + color: #a9b4bf; + font-size: 1rem; + line-height: 1.72; +} +html:not(.landing-page) .md-typeset h1, +html:not(.landing-page) .md-typeset h2, +html:not(.landing-page) .md-typeset h3, +html:not(.landing-page) .md-typeset h4 { + color: #f8fafc; + font-family: Oswald, 'Oswald Fallback', sans-serif; + font-weight: 700; + text-transform: uppercase; +} +html:not(.landing-page) .md-typeset h1 { + max-width: 800px; + margin: 0 0 30px; + font-size: clamp(3.25rem, 4.2vw, 4.5rem); + line-height: .94; + letter-spacing: -.04em; +} +html:not(.landing-page) .md-typeset h1::before { + content: 'RAYFORCE / DOCUMENTATION'; + display: block; + margin-bottom: 24px; + padding-left: 46px; + background: linear-gradient(#e9a033,#e9a033) left center / 32px 1px no-repeat; + color: #f3c46b; + font: 600 .62rem/1.2 'JetBrains Mono', monospace; + letter-spacing: .13em; +} +html:not(.landing-page) .md-typeset h1 + p { + max-width: 760px; + color: #c1cad3; + font-size: 1.12rem; + line-height: 1.7; +} +html:not(.landing-page) .md-typeset h2 { + margin-top: 4rem; + padding-bottom: .65rem; + border-bottom: 1px solid rgba(233,160,51,.24); + font-size: clamp(2.15rem, 3vw, 3rem); + line-height: 1; + letter-spacing: -.025em; +} +html:not(.landing-page) .md-typeset h3 { + margin-top: 2.6rem; + color: #f3c46b; + font-size: 1.55rem; + line-height: 1.2; +} +html:not(.landing-page) .md-typeset p, +html:not(.landing-page) .md-typeset li { color: #a9b4bf; } +html:not(.landing-page) .md-typeset strong { color: #edf2f6; } +html:not(.landing-page) .md-typeset hr { + margin: 30px 0; + border-color: rgba(255,255,255,.12); +} +html:not(.landing-page) .md-typeset .md-button { + min-height: 50px; + margin-right: 8px; + padding: 13px 22px; + border-radius: 0; + font: 600 .76rem/1 'JetBrains Mono', monospace; + letter-spacing: .03em; +} +html:not(.landing-page) .md-typeset .md-button--primary { + color: #15100a; + border: 1px solid #e9a033; + background: #e9a033; + box-shadow: 0 10px 40px rgba(233,160,51,.16); +} +html:not(.landing-page) .md-typeset pre > code, +html:not(.landing-page) .md-typeset table:not([class]), +html:not(.landing-page) .md-typeset .admonition, +html:not(.landing-page) .md-typeset details { + border-radius: 0; + border-color: rgba(255,255,255,.12); + background-color: rgba(11,16,23,.88); +} +html:not(.landing-page) .md-footer { + background: #06090d; + border-top: 1px solid rgba(255,255,255,.1); +} + +@media screen and (max-width: 76.234375em) { + html:not(.landing-page) .md-main__inner { + width: min(calc(100% - 48px), 920px); + display: block; + padding: 58px 0 100px; + } + html:not(.landing-page) .md-content { width: 100%; } + html:not(.landing-page) .md-sidebar--primary { + left: 0; + width: 100vw; + max-width: 100vw; + transform: translateX(-100%); + } + html:not(.landing-page) #__drawer:checked ~ .md-container .md-sidebar--primary { + transform: translateX(0); + } + html:not(.landing-page) .md-sidebar--primary .md-sidebar__scrollwrap { + background: #080b10; + } + html:not(.landing-page) .md-sidebar--primary .md-nav--primary { + width: min(calc(100% - 32px), 520px); + margin-inline: auto; + } + html:not(.landing-page) .md-sidebar--primary .md-sidebar__inner { + padding: 1.2rem; + border: 0; + background: #0b1017; + } +} + +@media screen and (max-width: 76.234375em) { + html:not(.landing-page) .docs-site-nav { height: 70px; } + html:not(.landing-page) .docs-site-nav .nav-inner { width: calc(100% - 32px); } + html:not(.landing-page) .docs-site-nav .nav-brand img, + html:not(.landing-page) .docs-site-nav .nav-brand svg { width: 116px; } + html:not(.landing-page) .docs-site-nav .nav-toggle { + width: 42px; + height: 42px; + display: block; + padding: 10px; + color: #f8fafc; + cursor: pointer; + } + html:not(.landing-page) .docs-site-nav .nav-toggle span { + display: block; + width: 21px; + height: 1px; + margin: 5px auto; + background: currentColor; + transition: .2s; + } + html:not(.landing-page) .docs-site-nav .nav-toggle.open span:first-child { transform: translateY(6px) rotate(45deg); } + html:not(.landing-page) .docs-site-nav .nav-toggle.open span:nth-child(2) { opacity: 0; } + html:not(.landing-page) .docs-site-nav .nav-toggle.open span:last-child { transform: translateY(-6px) rotate(-45deg); } + html:not(.landing-page) .docs-site-nav .nav-links { + display: none; + position: fixed; + z-index: 1; + top: calc(var(--rf-banner-h, 0px) + 70px); + right: 0; + left: 0; + height: calc(100svh - var(--rf-banner-h, 0px) - 70px); + flex-direction: column; + align-items: stretch; + justify-content: center; + padding: clamp(28px, 8vw, 72px); + overflow: auto; + background: rgba(8,11,16,.99); + border-top: 1px solid rgba(255,255,255,.11); + } + html:not(.landing-page) .docs-site-nav .nav-links.open { display: flex; } + html:not(.landing-page) .docs-site-nav .nav-links > a, + html:not(.landing-page) .docs-search-trigger, + html:not(.landing-page) .docs-browse-trigger { + width: 100%; + height: auto; + min-height: 0; + display: flex; + align-items: center; + justify-content: flex-start; + gap: 14px; + margin: 0; + padding: 12px 0; + border: 0; + border-bottom: 1px solid rgba(255,255,255,.11); + background: none; + color: #a8b3bd; + font: 600 clamp(1.8rem, 7vw, 3.3rem)/1 Oswald, 'Oswald Fallback', sans-serif; + letter-spacing: -.02em; + text-transform: uppercase; + cursor: pointer; + } + html:not(.landing-page) .docs-site-nav .nav-links > a:hover, + html:not(.landing-page) .docs-site-nav .nav-links > a.active, + html:not(.landing-page) .docs-search-trigger:hover, + html:not(.landing-page) .docs-browse-trigger:hover { + color: #f3c46b; + background: none; + } + html:not(.landing-page) .docs-search-trigger > span { display: inline; } + html:not(.landing-page) .docs-search-trigger svg { width: 24px; height: 24px; } + html:not(.landing-page) .docs-site-nav .nav-github { + width: 100%; + flex: 0 0 auto; + margin-top: 12px; + border-bottom: 0; + } + html:not(.landing-page) .docs-site-nav .nav-github svg { width: 24px; height: 24px; } + html:not(.landing-page) .md-container { padding-top: 70px; } + html:not(.landing-page) .md-main { min-height: calc(100svh - 70px); } + html:not(.landing-page) .md-main::before { width: 100%; opacity: .07; } + html:not(.landing-page) .md-sidebar--primary { + top: calc(var(--rf-banner-h, 0px) + 70px); + height: calc(100svh - var(--rf-banner-h, 0px) - 70px); + } +} + +@media screen and (max-width: 600px) { + html:not(.landing-page) .md-main__inner { + width: calc(100% - 32px); + padding: 44px 0 80px; + } + html:not(.landing-page) .md-typeset h1 { + font-size: clamp(2.8rem, 14vw, 4rem); + } + html:not(.landing-page) .md-typeset h1::before { + margin-bottom: 20px; + padding-left: 34px; + background-size: 22px 1px; + font-size: .56rem; + } + html:not(.landing-page) .md-typeset h1 + p { font-size: 1rem; } + html:not(.landing-page) .md-typeset .md-button { + display: table; + width: fit-content; + max-width: 100%; + margin: 0 0 10px; + } +} diff --git a/docs/assets/landing.css b/docs/assets/landing.css index f292e75da..de2109a35 100644 --- a/docs/assets/landing.css +++ b/docs/assets/landing.css @@ -1,971 +1,186 @@ -/* ── Self-hosted fonts (latin subset) ────────── */ -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url('/assets/fonts/inter-400.woff2') format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 600; - font-display: swap; - src: url('/assets/fonts/inter-600.woff2') format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 700; - font-display: swap; - src: url('/assets/fonts/inter-700.woff2') format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} -@font-face { - font-family: 'Oswald'; - font-style: normal; - font-weight: 600; - font-display: swap; - src: url('/assets/fonts/oswald-600.woff2') format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} -@font-face { - font-family: 'Oswald'; - font-style: normal; - font-weight: 700; - font-display: swap; - src: url('/assets/fonts/oswald-700.woff2') format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} -@font-face { - font-family: 'JetBrains Mono'; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url('/assets/fonts/jbmono-400.woff2') format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} -@font-face { - font-family: 'JetBrains Mono'; - font-style: normal; - font-weight: 600; - font-display: swap; - src: url('/assets/fonts/jbmono-600.woff2') format('woff2'); - unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} - -/* ── Fallback font-face overrides ───────────────────────────── - Make the system fallback render at the same visual metrics as - the web font, so swapping from fallback -> web font produces - no layout shift. Used as the second name in the font stack. - font-weight: 100 900 ensures the override applies to ALL - weights (including 500/600/700) — without it the @font-face - defaults to weight 400 only and bold text bypasses it. */ -@font-face { - font-family: 'Inter Fallback'; - src: local('Arial'), local('Helvetica'); - font-weight: 100 900; - size-adjust: 107%; - ascent-override: 90%; - descent-override: 22%; - line-gap-override: 0%; -} -@font-face { - font-family: 'Oswald Fallback'; - src: local('Arial Narrow'), local('Impact'), local('Arial'); - font-weight: 100 900; - size-adjust: 87%; - ascent-override: 100%; - descent-override: 31%; - line-gap-override: 0%; -} -@font-face { - font-family: 'JetBrains Mono Fallback'; - src: local('Menlo'), local('Consolas'), local('Courier New'); - font-weight: 100 900; - size-adjust: 99%; - ascent-override: 100%; - descent-override: 22%; - line-gap-override: 0%; -} - -/* ============================================================ - Rayforce — rayforce website - Bold / Editorial dark theme - Brand: #e9a033, accent: #5eead4, Oswald headings, Inter body - ============================================================ */ - -*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; } - -:root { - --bg: #0a0e13; - --bg-section: #0d1219; - --bg-card: #111820; - --border: rgba(233, 160, 51, 0.1); - --border-hover: rgba(233, 160, 51, 0.25); - --primary: #e9a033; - --primary-light: #f0b954; - --primary-lighter: #f5d080; - --primary-pale: #fdf0d8; - --primary-dark: #c4821a; - --accent: #5eead4; - --accent-blue: #60a5fa; - --navy: #0e1b24; - --text: #f1f5f9; - --text-muted: #7b8fa0; - --text-dim: #3a4f5c; - --max-w: 1200px; - --font: 'Inter', 'Inter Fallback', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - --font-heading: 'Oswald', 'Oswald Fallback', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - --mono: 'JetBrains Mono', 'JetBrains Mono Fallback', 'SF Mono', SFMono-Regular, ui-monospace, Menlo, Consolas, monospace; -} - -html { scroll-behavior: smooth; scroll-padding-top: 80px; } - -body { - font-family: var(--font); - background: var(--bg); - color: var(--text); - line-height: 1.6; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - overflow-x: hidden; -} - -a { color: var(--primary-light); text-decoration: none; transition: color .15s; } -a:hover { color: var(--accent); } - -code { - font-family: var(--mono); - font-size: .85em; - background: rgba(233, 160, 51, 0.15); - color: var(--primary-lighter); - padding: 2px 6px; - border-radius: 4px; -} - -.container { max-width: var(--max-w); margin: 0 auto; padding: 0 32px; } - -/* ── Nav ─────────────────────────────────────── */ -.nav { - position: fixed; - /* Clear the in-flow release banner: main.js publishes --rf-banner-h as the - still-visible banner height (shrinks to 0 as it scrolls away / on dismiss), - so the floating pill drops just beneath it and rises back to 16px after. */ - top: calc(16px + var(--rf-banner-h, 0px)); - left: 50%; transform: translateX(-50%); - z-index: 1000; - background: rgba(10, 14, 19, 0.8); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - border: 1px solid var(--border); - border-radius: 100px; - padding: 0 8px; - /* Don't transition `all` — width changes inside the nav (e.g. the - count-up GitHub widget) would otherwise animate the centered bar - sliding sideways. Restrict to scroll-state visuals only. */ - transition: background .3s, border-color .3s, box-shadow .3s, top .25s ease; -} -.nav.nav-scrolled { - box-shadow: 0 4px 30px rgba(0, 0, 0, 0.4); - border-color: var(--border-hover); -} -.nav-inner { - display: flex; align-items: center; gap: 8px; - height: 64px; padding: 0 16px; -} -.nav-brand { - display: flex; align-items: center; - margin-right: 16px; text-decoration: none; -} -.nav-brand:hover { text-decoration: none; } -.nav-brand img { height: 40px; width: auto; } -.nav-links { - display: flex; align-items: center; gap: 4px; list-style: none; -} -.nav-links a { - color: var(--text-muted); font-size: .85rem; font-weight: 500; - padding: 6px 14px; border-radius: 100px; - transition: color .2s, background .2s; text-decoration: none; -} -.nav-links a:hover { - color: var(--text); background: rgba(233, 160, 51, 0.08); - text-decoration: none; -} -.nav-links a.active { color: var(--text); background: rgba(233, 160, 51, 0.1); } -.nav-cta { - display: inline-flex !important; align-items: center; gap: 6px; - background: linear-gradient(135deg, var(--primary), var(--primary-light)) !important; - color: var(--text) !important; font-weight: 600 !important; - margin-left: 8px; - box-shadow: 0 2px 12px rgba(233, 160, 51, 0.3); -} -.nav-cta:hover { - box-shadow: 0 4px 20px rgba(233, 160, 51, 0.5); - color: var(--text) !important; -} - -.nav-toggle { - display: none; background: none; border: none; cursor: pointer; padding: 4px; -} -.nav-toggle span { - display: block; width: 20px; height: 2px; background: var(--text); - margin: 4px 0; border-radius: 2px; transition: transform .25s, opacity .25s; -} -.nav-toggle.open span:nth-child(1) { transform: rotate(45deg) translate(4px,4px); } -.nav-toggle.open span:nth-child(2) { opacity: 0; } -.nav-toggle.open span:nth-child(3) { transform: rotate(-45deg) translate(4px,-4px); } - -/* ── Hero ────────────────────────────────────── */ -.hero { - position: relative; min-height: 100vh; - display: flex; align-items: center; - padding: 120px 0 80px; overflow: hidden; -} -.hero-bg { - position: absolute; inset: 0; pointer-events: none; -} -.hero-gradient { - position: absolute; inset: 0; - background: - radial-gradient(ellipse 100% 80% at 30% 20%, rgba(233, 160, 51, 0.15), transparent 60%), - radial-gradient(ellipse 80% 60% at 70% 80%, rgba(94, 234, 212, 0.06), transparent 50%), - radial-gradient(ellipse 50% 40% at 80% 20%, rgba(96, 165, 250, 0.06), transparent 50%); -} -.hero-grid-lines { - position: absolute; inset: 0; - background-image: - linear-gradient(rgba(233, 160, 51, 0.04) 1px, transparent 1px), - linear-gradient(90deg, rgba(233, 160, 51, 0.04) 1px, transparent 1px); - background-size: 80px 80px; - mask-image: radial-gradient(ellipse 80% 80% at 50% 50%, black 30%, transparent 70%); - -webkit-mask-image: radial-gradient(ellipse 80% 80% at 50% 50%, black 30%, transparent 70%); -} -.hero .container { position: relative; z-index: 1; } -.hero-content { max-width: 720px; } -.hero-logo { display: block; height: 64px; width: auto; margin-bottom: 32px; } -.hero-eyebrow { - font-family: var(--mono); font-size: .72rem; font-weight: 600; - letter-spacing: .16em; color: var(--accent); margin-bottom: 24px; -} -.hero h1 { - font-family: var(--font-heading); - font-size: 5rem; font-weight: 700; - line-height: 1.04; margin-bottom: 24px; letter-spacing: -.02em; -} -.highlight { - background: linear-gradient(135deg, var(--primary-lighter), var(--accent)); - -webkit-background-clip: text; -webkit-text-fill-color: transparent; - background-clip: text; -} -.hero-desc { - font-size: 1.15rem; color: var(--text-muted); - max-width: 580px; line-height: 1.7; margin-bottom: 36px; -} -.hero-desc a { color: var(--accent); font-weight: 600; } -/* Keep the three hero CTAs on a single row: nowrap + compact sizing so - Download / Docs / GitHub fit the (grid-narrowed) hero column. The full-size - .btn-hero padding stays on the centered CTA section, which has room. */ -.hero-actions { display: flex; gap: 10px; align-items: center; flex-wrap: nowrap; } -.hero-actions .btn-hero, -.hero-actions .btn-hero-ghost { - padding: 12px 20px; font-size: .9rem; gap: 7px; white-space: nowrap; -} - -/* OS-detected download note under the hero CTAs (filled in by landing.js; - stays hidden until a latest release resolves). */ -.hero-download-meta { - margin: 14px 0 0; font-family: var(--mono); - font-size: .8rem; color: var(--text-muted); -} - -/* Buttons */ -.btn-hero { - display: inline-flex; align-items: center; gap: 10px; - padding: 14px 32px; - background: linear-gradient(135deg, var(--primary), var(--primary-light)); - color: var(--text); border-radius: 100px; - font-size: .95rem; font-weight: 600; - transition: transform .2s, box-shadow .2s, background .2s; - box-shadow: 0 4px 20px rgba(233, 160, 51, 0.35); -} -.btn-hero:hover { - transform: translateY(-2px); - box-shadow: 0 8px 32px rgba(233, 160, 51, 0.5); - color: var(--text); -} -.btn-hero-ghost { - display: inline-flex; align-items: center; gap: 8px; - padding: 14px 32px; color: var(--text-muted); - border: 1.5px solid var(--border); border-radius: 100px; - font-size: .95rem; font-weight: 600; - transition: color .2s, border-color .2s, background .2s; -} -.btn-hero-ghost:hover { - color: var(--text); border-color: var(--border-hover); - background: rgba(233, 160, 51, 0.05); -} - -/* Hero Stats — display-sized horizontal strip. Three statements, - no chrome. Replaces the older .hero-numbers icon-card grid which - read 2021-Strapi. Reads as a single sentence: "16K · 0 · 7". */ -.hero-stats { - display: grid; grid-template-columns: repeat(3, 1fr); - gap: 32px; - padding-top: 56px; - border-top: 1px solid var(--border); -} -.hero-stat { - /* Scroll-triggered: fades in via .visible class added by landing.js. */ - opacity: 0; transform: translateY(16px); - transition: opacity .6s ease, transform .6s ease; -} -.hero-stat.visible { opacity: 1; transform: translateY(0); } -.hero-stat-num { - display: block; - font-family: var(--font-heading); - font-size: clamp(3.5rem, 8vw, 6rem); - font-weight: 700; - line-height: 1; letter-spacing: -.04em; - color: var(--text); - margin-bottom: 14px; - /* Subtle accent on the digits — kept low-key so it doesn't - compete with the .highlight gradient in the headline. */ - background: linear-gradient(180deg, var(--text) 0%, rgba(241, 245, 249, 0.55) 100%); - -webkit-background-clip: text; background-clip: text; - -webkit-text-fill-color: transparent; -} -.hero-stat-label { - display: block; - font-family: var(--mono); font-size: .72rem; font-weight: 600; - letter-spacing: .14em; text-transform: uppercase; - color: var(--text-muted); -} - -/* ── Section Tag ─────────────────────────────── */ -.section-tag { - font-family: var(--mono); font-size: .7rem; font-weight: 600; - letter-spacing: .16em; color: var(--accent); - display: block; margin-bottom: 16px; -} - -/* ── Why Section ─────────────────────────────── */ -.why-section { - padding: 120px 0; - background: var(--bg-section); - border-top: 1px solid var(--border); -} -.why-header { max-width: 600px; margin-bottom: 56px; } -.why-header h2 { - font-family: var(--font-heading); font-size: 3rem; font-weight: 700; - line-height: 1.1; margin-bottom: 16px; -} -.text-gradient { - background: linear-gradient(135deg, var(--primary-lighter), var(--accent)); - -webkit-background-clip: text; -webkit-text-fill-color: transparent; - background-clip: text; -} -.why-header p { - font-size: 1.05rem; color: var(--text-muted); line-height: 1.7; -} -.why-grid { - display: grid; grid-template-columns: 1.5fr 1fr 1fr; - grid-template-rows: auto auto auto; gap: 16px; -} -.why-card-small { - background: var(--bg-card); border: 1px solid var(--border); - border-radius: 16px; padding: 28px; - opacity: 0; transform: translateY(24px); - transition: opacity .5s ease, transform .5s ease, border-color .2s, box-shadow .3s; -} -.why-card-small.visible { opacity: 1; transform: translateY(0); } -.why-card-small:hover { border-color: var(--border-hover); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); } -.why-card-small .why-card-label { margin-bottom: 10px; } -.why-card-desc { - font-size: .88rem; color: var(--text-muted); line-height: 1.6; -} -.why-card { - background: var(--bg-card); border: 1px solid var(--border); - border-radius: 16px; padding: 28px; - opacity: 0; transform: translateY(24px); - transition: opacity .5s ease, transform .5s ease, border-color .2s, box-shadow .3s; -} -.why-card.visible { opacity: 1; transform: translateY(0); } -.why-card:hover { border-color: var(--border-hover); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); } -.why-card-large { grid-row: 1 / 3; } -.why-card-label { - font-family: var(--font-heading); font-size: .7rem; font-weight: 600; - letter-spacing: .12em; color: var(--accent); margin-bottom: 16px; -} -.why-card-label-dim { color: var(--text-dim); } -.why-list { list-style: none; } -.why-list li { - padding: 7px 0; font-size: .9rem; color: var(--text-muted); - line-height: 1.5; border-bottom: 1px solid rgba(233, 160, 51, 0.06); -} -.why-list li:last-child { border-bottom: none; } -.why-list-solution li::before { content: '\2713\00a0'; color: var(--accent); font-weight: 600; } -.why-list-problem li::before { content: '\2013\00a0'; color: var(--text-dim); } - -/* ── Features ────────────────────────────────── */ -.features-section { - padding: 120px 0; - border-top: 1px solid var(--border); -} -.features-title { - font-family: var(--font-heading); font-size: 3rem; font-weight: 700; - margin-bottom: 8px; -} -.features-subtitle { - font-size: 1.05rem; color: var(--text-muted); - max-width: 560px; margin-bottom: 56px; -} -.features-grid { - display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; -} -.feature-card { - background: var(--bg-card); border: 1px solid var(--border); - border-radius: 16px; padding: 28px; - opacity: 0; transform: translateY(24px); - transition: opacity .5s ease, transform .5s ease, border-color .2s, box-shadow .3s; -} -.feature-card.visible { opacity: 1; transform: translateY(0); } -.feature-card:hover { - border-color: var(--border-hover); - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); -} -.feature-card-header { - display: flex; align-items: center; gap: 12px; margin-bottom: 12px; -} -.feature-card-header svg { - width: 24px; height: 24px; color: var(--accent); flex-shrink: 0; -} -.feature-card-header h3 { - font-family: var(--font-heading); font-size: 1.2rem; font-weight: 600; -} -.feature-card p { - font-size: .9rem; color: var(--text-muted); line-height: 1.65; -} -.feature-card p a { color: var(--accent); } - -/* ── Architecture ────────────────────────────── */ -.arch-section { - padding: 120px 0; - background: var(--bg-section); - border-top: 1px solid var(--border); -} -.arch-section .section-tag { text-align: center; } -.arch-title { - font-family: var(--font-heading); font-size: 3rem; font-weight: 700; - text-align: center; margin-bottom: 8px; -} -.arch-subtitle { - text-align: center; color: var(--text-muted); margin-bottom: 48px; - font-size: 1.05rem; max-width: 640px; margin-left: auto; margin-right: auto; -} -.arch-diagram { - max-width: 900px; margin: 0 auto; - background: var(--bg); border: 1px solid var(--border); - border-radius: 16px; padding: 32px 24px; - box-shadow: 0 24px 80px rgba(0, 0, 0, 0.3); - overflow: hidden; -} -.arch-svg { width: 100%; height: auto; display: block; } -.arch-core-rect { animation: core-pulse 4s ease-in-out infinite; } -.arch-caption { - text-align: center; color: var(--text-muted); margin-top: 24px; - font-size: .9rem; max-width: 680px; margin-left: auto; margin-right: auto; -} -.arch-caption strong { color: var(--text); } -.arch-caption a { color: var(--accent); } - -/* Legacy arch-flow styles (for docs pages) */ -.arch-flow { - display: flex; align-items: center; justify-content: center; - gap: 24px; flex-wrap: wrap; color: #e2e8f0; - font-family: var(--mono); font-size: .85rem; -} -.arch-node { - background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.12); - border-radius: 12px; padding: 16px 20px; text-align: center; -} -.arch-node-label { - font-family: var(--font-heading); font-size: .7rem; font-weight: 600; - text-transform: uppercase; letter-spacing: .1em; - color: rgba(255,255,255,.4); margin-bottom: 8px; -} -.arch-node-items { display: flex; flex-direction: column; gap: 4px; } -.arch-node-items span { color: #60a5fa; } -.arch-arrow { color: var(--primary-light); font-size: 1.5rem; font-weight: 300; } -.arch-core { - background: rgba(233,160,51,.3); border: 1px solid rgba(233,160,51,.5); - border-radius: 16px; padding: 20px 28px; -} -.arch-core-title { - font-family: var(--font-heading); font-size: 1rem; font-weight: 700; - color: var(--primary-light); margin-bottom: 12px; text-align: center; -} -.arch-core-tools { display: flex; gap: 12px; } -.arch-tool { - background: rgba(255,255,255,.06); border-radius: 8px; - padding: 8px 14px; font-size: .8rem; color: #e2e8f0; -} - -/* ── Demo / Terminal ─────────────────────────── */ -.demo-section { - padding: 120px 0; - border-top: 1px solid var(--border); -} -.demo-title { - font-family: var(--font-heading); font-size: 3rem; font-weight: 700; - margin-bottom: 8px; -} -.demo-subtitle { - font-size: 1.05rem; color: var(--text-muted); - max-width: 520px; margin-bottom: 48px; -} -.demo-terminal { - background: var(--bg-section); border: 1px solid var(--border); - border-radius: 16px; overflow: hidden; - max-width: 800px; - box-shadow: 0 24px 80px rgba(0, 0, 0, 0.3); -} -.terminal-header { - display: flex; align-items: center; gap: 8px; - padding: 14px 18px; - background: rgba(255, 255, 255, 0.02); - border-bottom: 1px solid var(--border); -} -.dot { width: 10px; height: 10px; border-radius: 50%; } -.dot.red { background: #ef4444; } -.dot.yellow { background: #eab308; } -.dot.green { background: #22c55e; } -.terminal-title { - font-family: var(--mono); font-size: .75rem; - color: var(--text-dim); margin-left: 8px; -} -.terminal-body { - padding: 20px 24px; height: 380px; overflow: auto; - font-family: var(--mono); font-size: .85rem; - line-height: 1.75; color: #e2e8f0; - scrollbar-width: thin; - scrollbar-color: rgba(255,255,255,.08) transparent; -} -.terminal-body::-webkit-scrollbar { width: 6px; height: 6px; } -.terminal-body::-webkit-scrollbar-track { background: transparent; } -.terminal-body::-webkit-scrollbar-thumb { background: rgba(255,255,255,.08); border-radius: 3px; } -.demo-mcp { - color: var(--text-muted); margin-top: 20px; font-size: .9rem; max-width: 800px; -} - -.term-line { white-space: pre; min-height: 1.75em; } -.term-cmd { color: #e2e8f0; } -.term-out { color: #94a3b8; } -.t-ok { color: #4ade80; font-weight: 600; } -.t-url { color: var(--accent); } -.t-cmd { color: var(--accent); } -.t-key { color: #60a5fa; } -.t-str { color: #4ade80; } -.t-num { color: #f59e0b; } -.t-tbl { color: #60a5fa; font-weight: 600; } -.t-dim { color: #64748b; } -.t-prompt { color: #4ade80; } -.term-cursor { - color: #e2e8f0; - animation: cursor-blink .7s step-end infinite; -} - -/* ── Use Cases ───────────────────────────────── */ -.usecases-section { - padding: 120px 0; - background: var(--bg-section); - border-top: 1px solid var(--border); -} -.usecases-header { - text-align: center; margin-bottom: 56px; -} -.usecases-header .section-tag { text-align: center; } -.usecases-header h2 { - font-family: var(--font-heading); font-size: 3rem; font-weight: 700; -} -.usecases-strip { - display: flex; align-items: stretch; - background: var(--bg-card); border: 1px solid var(--border); - border-radius: 16px; overflow: hidden; -} -.usecase-item { - flex: 1; padding: 36px; - opacity: 0; transform: translateY(20px); - transition: opacity .5s ease, transform .5s ease, background .2s; -} -.usecase-item.visible { opacity: 1; transform: translateY(0); } -.usecase-item:hover { background: rgba(233, 160, 51, 0.04); } -.usecase-divider { - width: 1px; background: var(--border); align-self: stretch; -} -.usecase-icon { - width: 48px; height: 48px; margin-bottom: 16px; - color: var(--accent); -} -.usecase-icon svg { width: 100%; height: 100%; } -.usecase-item h3 { - font-family: var(--font-heading); font-size: 1.15rem; font-weight: 600; - margin-bottom: 10px; -} -.usecase-item p { - font-size: .88rem; color: var(--text-muted); line-height: 1.65; -} - -/* ── CTA ─────────────────────────────────────── */ -.cta-section { - padding: 120px 0; - background: var(--bg-section); - border-top: 1px solid var(--border); -} -.cta-content { - text-align: center; max-width: 700px; margin: 0 auto; -} -.cta-content h2 { - font-family: var(--font-heading); font-size: 3.5rem; font-weight: 700; - line-height: 1.08; margin-bottom: 40px; -} -.cta-terminal { - background: var(--bg-section); border: 1px solid var(--border); - border-radius: 12px; overflow: hidden; text-align: left; - margin-bottom: 40px; - box-shadow: 0 24px 80px rgba(0, 0, 0, 0.3); -} -.cta-terminal-body { - padding: 20px 24px; font-family: var(--mono); - font-size: .85rem; line-height: 1.8; color: var(--text-muted); - overflow-x: auto; -} -.cta-terminal-body code { - background: none; padding: 0; font-size: inherit; color: inherit; -} -.cta-buttons { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; } - -/* ── Footer ──────────────────────────────────── */ -.footer { - padding: 22px 0; border-top: 1px solid var(--border); -} -.footer-inner { - display: flex; align-items: center; justify-content: space-between; - gap: 16px; -} -.footer-brand { - display: flex; align-items: center; gap: 8px; - font-family: var(--font-heading); font-size: 1rem; font-weight: 600; - color: var(--text-dim); text-transform: uppercase; letter-spacing: .04em; -} -.footer-links { display: flex; gap: 24px; } -.footer-links a { font-size: .85rem; color: var(--text-muted); } -.footer-links a:hover { color: var(--text); } - -/* ── Scroll-to-top ───────────────────────────── */ -.scroll-top { - position: fixed; bottom: 28px; right: 28px; - width: 44px; height: 44px; border-radius: 50%; - background: var(--primary); color: var(--text); - border: none; cursor: pointer; - display: flex; align-items: center; justify-content: center; - box-shadow: 0 4px 16px rgba(233, 160, 51, 0.3); - opacity: 0; transform: translateY(12px); - transition: opacity .3s, transform .3s, background .2s; - pointer-events: none; z-index: 999; -} -.scroll-top.visible { opacity: 1; transform: translateY(0); pointer-events: auto; } -.scroll-top:hover { background: var(--primary-light); transform: translateY(-2px); } -.scroll-top svg { width: 20px; height: 20px; } - -/* ── Animations ──────────────────────────────── */ -@keyframes cursor-blink { - 0%, 100% { opacity: 1; } - 50% { opacity: 0; } -} -@keyframes core-pulse { - 0%, 100% { stroke-opacity: 0.5; } - 50% { stroke-opacity: 0.85; } -} - -/* ── Responsive ──────────────────────────────── */ -@media (max-width: 1024px) { - .hero h1 { font-size: 3.8rem; } - .hero-stats { gap: 24px; } - .why-grid { grid-template-columns: 1fr 1fr; } - .why-card-large { grid-row: auto; grid-column: 1 / -1; } - .features-grid { grid-template-columns: repeat(2, 1fr); } - .usecases-strip { flex-direction: column; } - .usecase-divider { width: auto; height: 1px; } -} -@media (max-width: 768px) { - .nav { top: 8px; left: 8px; right: 8px; transform: none; border-radius: 16px; padding: 0; } - .nav-inner { padding: 0 12px; justify-content: space-between; } - .nav-toggle { display: block; } - .nav-links { - display: none; position: absolute; top: 56px; left: 0; right: 0; - background: rgba(10, 14, 19, 0.98); backdrop-filter: blur(20px); - flex-direction: column; padding: 16px; gap: 4px; - border-radius: 0 0 16px 16px; - border-top: 1px solid var(--border); - box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3); - } - .nav-links.open { display: flex; } - .hero { min-height: auto; padding: 100px 0 60px; } - .hero h1 { font-size: 2.8rem; } - .hero-stats { grid-template-columns: 1fr; gap: 32px; padding-top: 40px; } - .hero-stat-num { font-size: clamp(3rem, 14vw, 5rem); } - .why-grid { grid-template-columns: 1fr; } - .features-grid { grid-template-columns: 1fr; } - .terminal-body { height: 280px; font-size: .78rem; padding: 16px 18px; } - .footer-inner { flex-direction: column; gap: 16px; text-align: center; } - .footer-links { flex-wrap: wrap; justify-content: center; } - .arch-diagram { padding: 20px 12px; } - .features-title, .demo-title, .arch-title, - .why-header h2, .usecases-header h2 { font-size: 2.2rem; } - .cta-content h2 { font-size: 2.5rem; } -} -@media (max-width: 480px) { - .hero h1 { font-size: 2.2rem; } - .container { padding: 0 16px; } -} - -/* ── Utilities ───────────────────────────────── */ -::selection { background: rgba(94, 234, 212, 0.2); color: inherit; } -:focus-visible { outline: 2px solid var(--primary-light); outline-offset: 2px; } -@media (prefers-reduced-motion: reduce) { - html { scroll-behavior: auto; } - .why-card, .why-card-small, .feature-card, .usecase-item, - .hero-stat { opacity: 1; transform: none; transition: none; } - .arch-core-rect { animation: none; } - .term-cursor { animation: none; opacity: 1; } -} - -/* ── About page: fullscreen promo iframe with floating pill nav on top ─── */ -.promo-frame { - display: block; - width: 100vw; - height: 100vh; - border: 0; - background: var(--bg); -} - -/* ── About page content ──────────────────────── */ -.about-content { - background: var(--bg); color: var(--text); - padding: 64px 0 96px; -} -.about-content .container { max-width: 760px; } -.about-content h1 { - font-family: var(--font-heading); - font-size: 2.4rem; font-weight: 600; letter-spacing: -.01em; - color: var(--text); margin: 0 0 16px; -} -.about-content h2 { - font-family: var(--font-heading); - font-size: 1.4rem; font-weight: 600; color: var(--text); - margin: 40px 0 12px; text-transform: uppercase; letter-spacing: .04em; -} -.about-content .about-lede { - font-size: 1.15rem; line-height: 1.6; color: var(--text-muted); - margin: 0 0 24px; -} -.about-content p { line-height: 1.65; color: var(--text-muted); margin: 0 0 16px; } -.about-content ul { padding-left: 20px; margin: 0 0 16px; } -.about-content li { line-height: 1.65; color: var(--text-muted); margin-bottom: 10px; } -.about-content strong { color: var(--text); } -.about-content a { - color: var(--primary-light); border-bottom: 1px solid rgba(233, 160, 51, 0.25); -} -.about-content a:hover { color: var(--accent); border-bottom-color: currentColor; } -.about-content code { - font-family: var(--mono); - font-size: .92em; background: rgba(233, 160, 51, 0.1); - color: var(--primary-lighter); - padding: 1px 6px; border-radius: 3px; -} - -/* ── Mobile tap targets (48x48 minimum) ──────── */ -@media (max-width: 768px) { - .nav-links a { padding: 14px 16px; min-height: 48px; display: flex; align-items: center; } - .nav-toggle { padding: 12px; min-width: 44px; min-height: 44px; } - .footer-links { gap: 4px 18px; } - .footer-links a { padding: 10px 4px; min-height: 44px; display: inline-flex; align-items: center; } -} -.scroll-top { width: 48px; height: 48px; } - -/* ── About page: allow scrolling so content below the iframe is reachable ─── */ -body.about-page { margin: 0; overflow: auto; } - -/* ────────────────────────────────────────────── - Nav: Live Demo highlight + GitHub widget - ────────────────────────────────────────────── */ -.nav-link-demo { - display: inline-flex; align-items: center; gap: 5px; - padding: 6px 10px; border-radius: 100px; - background: rgba(94, 234, 212, 0.08); - color: var(--accent, #5eead4) !important; - border: 1px solid rgba(94, 234, 212, 0.25); - font-weight: 600 !important; - white-space: nowrap; - flex-shrink: 0; - transition: background .15s, border-color .15s, color .15s; -} -.nav-link-demo:hover { - background: rgba(94, 234, 212, 0.16); - border-color: rgba(94, 234, 212, 0.4); -} -.nav-link-demo svg { color: currentColor; } - -.github-widget { - display: inline-flex; align-items: stretch; - margin-left: 4px; - border: 1px solid var(--border); - border-radius: 8px; - background: rgba(255, 255, 255, 0.02); - overflow: hidden; - flex-shrink: 0; - font-family: var(--mono); - font-size: .78rem; - line-height: 1; -} -.github-widget a { - display: inline-flex; align-items: center; gap: 5px; - padding: 6px 10px; - color: var(--text-muted) !important; - text-decoration: none; - background: none; - transition: background .15s, color .15s; -} -.github-widget a + a { border-left: 1px solid var(--border); } -.github-widget a:hover { - color: var(--text) !important; - background: rgba(233, 160, 51, 0.08); -} -.github-widget-icon { padding: 6px 9px !important; } -.github-widget-icon svg { color: var(--text); } -.github-widget [data-gh-stat] { - display: inline-block; - font-feature-settings: 'tnum' on; - font-variant-numeric: tabular-nums; - font-weight: 600; - color: var(--text); - min-width: 3.5ch; - text-align: left; -} - -/* ────────────────────────────────────────────── - Hero: 2-column top with Python pillar - ────────────────────────────────────────────── */ -.hero-top { - display: grid; - grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr); - gap: 56px; - align-items: center; - margin-bottom: 64px; -} - -.hero-python { - position: relative; - background: linear-gradient(160deg, rgba(96, 165, 250, 0.06), rgba(94, 234, 212, 0.04) 60%, transparent); - border: 1px solid var(--border); - border-radius: 16px; - padding: 18px 20px 16px; - box-shadow: 0 12px 50px rgba(0, 0, 0, 0.25); -} -.hero-python-head { - display: flex; align-items: center; justify-content: space-between; - gap: 12px; - margin-bottom: 14px; -} -.hero-python-tag { - font-family: var(--mono); - font-size: .68rem; font-weight: 600; - letter-spacing: .18em; text-transform: uppercase; - color: var(--accent-blue); - padding: 4px 10px; - border: 1px solid rgba(96, 165, 250, 0.25); - background: rgba(96, 165, 250, 0.08); - border-radius: 100px; -} -.hero-python-link { - font-family: var(--mono); - font-size: .75rem; - color: var(--text-muted); - display: inline-flex; align-items: center; gap: 6px; - text-decoration: none; - transition: color .15s; -} -.hero-python-link:hover { color: var(--text); } - -.hero-python-code { - margin: 0; - padding: 16px 18px; - background: rgba(0, 0, 0, 0.3); - border: 1px solid var(--border); - border-radius: 10px; - font-family: var(--mono); - font-size: .82rem; - line-height: 1.65; - color: var(--text); - overflow-x: auto; -} -.hero-python-code code { background: none; padding: 0; color: inherit; font-size: inherit; } -.hero-python-code .hl-kw { color: var(--primary-light); font-weight: 600; } -.hero-python-code .hl-str { color: #4ade80; } -.hero-python-code .hl-num { color: #f59e0b; } -.hero-python-code .hl-comment { color: var(--text-dim); } - -.hero-python-foot { - display: flex; align-items: center; gap: 8px; - margin-top: 14px; -} -.hero-python-cta { - flex: 1; - text-align: center; - padding: 9px 14px; - font-size: .85rem; font-weight: 600; - text-decoration: none; - border-radius: 100px; - background: linear-gradient(135deg, var(--primary), var(--primary-light)); - color: var(--text); - box-shadow: 0 2px 12px rgba(233, 160, 51, 0.3); - transition: box-shadow .15s, transform .15s; -} -.hero-python-cta:hover { - box-shadow: 0 4px 20px rgba(233, 160, 51, 0.5); - transform: translateY(-1px); - color: var(--text); -} -.hero-python-pypi { - padding: 9px 14px; - font-size: .85rem; font-weight: 600; - color: var(--text-muted); - text-decoration: none; - border: 1px solid var(--border); - border-radius: 100px; - transition: color .15s, border-color .15s, background .15s; -} -.hero-python-pypi:hover { - color: var(--text); - border-color: var(--border-hover); - background: rgba(233, 160, 51, 0.05); -} - -@media (max-width: 1024px) { - .hero-top { grid-template-columns: 1fr; gap: 40px; } - .hero-python-code { font-size: .78rem; padding: 14px 16px; } -} - -/* Mobile alignment for nav widgets */ -@media (max-width: 768px) { - .github-widget { - margin-left: 0; - align-self: flex-start; - } - .nav-link-demo { width: fit-content; } - .python-section { padding: 80px 0; } - .python-title { font-size: 2rem; } -} +/* Rayforce marketing surface — full-screen editorial redesign */ +@font-face{font-family:Inter;font-style:normal;font-weight:400;font-display:block;src:url('/assets/fonts/inter-400.woff2') format('woff2')} +@font-face{font-family:Inter;font-style:normal;font-weight:600;font-display:block;src:url('/assets/fonts/inter-600.woff2') format('woff2')} +@font-face{font-family:Inter;font-style:normal;font-weight:700;font-display:swap;src:url('/assets/fonts/inter-700.woff2') format('woff2')} +@font-face{font-family:Oswald;font-style:normal;font-weight:600;font-display:swap;src:url('/assets/fonts/oswald-600.woff2') format('woff2')} +@font-face{font-family:Oswald;font-style:normal;font-weight:700;font-display:block;src:url('/assets/fonts/oswald-700.woff2') format('woff2')} +@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:400;font-display:block;src:url('/assets/fonts/jbmono-400.woff2') format('woff2')} +@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:600;font-display:block;src:url('/assets/fonts/jbmono-600.woff2') format('woff2')} + +:root{ + --ink:#080b10; + --ink-2:#0c1118; + --ink-3:#111923; + --panel:#121b25; + --paper:#f4f0e7; + --white:#f8fafc; + --muted:#8796a5; + --muted-2:#526170; + --amber:#e9a033; + --amber-light:#f3c46b; + --teal:#5eead4; + --blue:#60a5fa; + --line:rgba(255,255,255,.11); + --line-strong:rgba(255,255,255,.2); + --max:1380px; + --sans:Inter,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; + --display:Oswald,'Arial Narrow',sans-serif; + --mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,monospace; +} + +*,*::before,*::after{box-sizing:border-box} +html{scroll-behavior:smooth;scroll-padding-top:96px} +body{margin:0;background:var(--ink);color:var(--white);font-family:var(--sans);line-height:1.5;-webkit-font-smoothing:antialiased;overflow-x:hidden} +a{color:inherit;text-decoration:none} +button{font:inherit} +.rf-container{width:min(calc(100% - 80px),var(--max));margin-inline:auto} +.rf-screen{position:relative;min-height:100svh;display:flex;flex-direction:column;justify-content:center;overflow:hidden;border-bottom:1px solid var(--line)} +.rf-kicker{display:flex;align-items:center;gap:12px;margin:0 0 24px;color:var(--amber-light);font-family:var(--mono);font-size:.72rem;font-weight:600;letter-spacing:.13em;text-transform:uppercase} +.rf-kicker>span{display:inline-block;width:34px;height:1px;background:currentColor} +.rf-kicker b{padding:4px 8px;border:1px solid rgba(233,160,51,.32);border-radius:999px;color:var(--amber);font-size:.62rem;letter-spacing:.08em} +.rf-button{min-height:50px;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;gap:12px;padding:13px 22px;border:1px solid var(--line-strong);font-family:var(--mono);font-size:.76rem;font-weight:600;letter-spacing:.03em;white-space:nowrap;transition:transform .2s,border-color .2s,color .2s,background .2s,box-shadow .2s} +.rf-button:hover{transform:translateY(-2px);border-color:rgba(233,160,51,.6);color:var(--amber-light)} +.rf-button svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round} +.rf-button-icon{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto}.rf-button .rf-button-icon svg{width:18px;height:18px;fill:currentColor;stroke:none} +.rf-button--primary{background:var(--amber);border-color:var(--amber);color:#15100a;box-shadow:0 10px 40px rgba(233,160,51,.16)} +.rf-button--primary:hover{background:var(--amber-light);border-color:var(--amber-light);color:#15100a;box-shadow:0 16px 54px rgba(233,160,51,.24)} +.rf-button--cloud{border-color:rgba(94,234,212,.38);background:rgba(94,234,212,.055);color:#d7fffa}.rf-button--cloud:hover{border-color:var(--teal);background:rgba(94,234,212,.1);color:var(--teal)}.rf-button--cloud small{padding:3px 6px;border:1px solid rgba(94,234,212,.32);color:var(--teal);font:600 .5rem var(--mono);letter-spacing:.08em;text-transform:uppercase} +.rf-button--quiet{background:rgba(255,255,255,.02)} +.rf-text-link{display:inline-flex;gap:12px;align-items:center;margin-top:30px;color:var(--amber-light);font-family:var(--mono);font-size:.76rem;font-weight:600} +.rf-text-link span{transition:transform .2s}.rf-text-link:hover span{transform:translate(3px,-3px)} +.reveal{opacity:1;transform:none;transition:opacity .7s ease,transform .7s cubic-bezier(.22,1,.36,1)} +.has-reveal .reveal{opacity:0;transform:translateY(26px)} +.reveal.is-visible{opacity:1;transform:none} + +/* Navigation */ +.nav{position:fixed;z-index:1000;top:var(--rf-banner-h,0px);left:0;width:100%;height:76px;background:rgba(8,11,16,.74);border-bottom:1px solid var(--line);backdrop-filter:blur(18px);-webkit-backdrop-filter:blur(18px);transition:background .25s,border-color .25s} +.nav.nav-scrolled{background:rgba(8,11,16,.96);border-color:rgba(233,160,51,.24)} +.nav-inner{height:100%;display:flex;align-items:center;padding:0} +.nav-brand{display:flex;align-items:center;margin-right:auto} +.nav-brand img{display:block;width:128px;height:auto} +.nav-links{display:flex;align-items:center;gap:3px} +.nav-links>a{padding:10px 15px;color:#b5c0ca;font-size:.88rem;font-weight:600;transition:color .2s,background .2s} +.nav-links>a:hover,.nav-links>a.active{color:var(--white);background:rgba(255,255,255,.05)} +.nav-cloud{display:flex!important;gap:7px;align-items:center} +.nav-cloud i{width:6px;height:6px;border-radius:50%;background:var(--amber);box-shadow:0 0 12px var(--amber)} +.nav-playground{display:flex!important;align-items:center;gap:6px;color:var(--amber-light)!important} +.nav-playground svg{width:12px;height:12px;fill:currentColor} +.nav-github{width:90px;display:flex;align-items:center;justify-content:center;flex:0 0 90px;gap:8px;margin-left:10px;padding:9px 12px;border:1px solid var(--line-strong);font-family:var(--mono);font-size:.76rem;color:var(--white);transition:border-color .2s,background .2s} +.nav-github:hover{border-color:var(--amber);background:rgba(233,160,51,.07)} +.nav-github svg{width:16px;height:16px;fill:currentColor} +.nav-toggle{display:none;width:42px;height:42px;padding:10px;border:0;background:transparent;color:var(--white)} +.nav-toggle span{display:block;width:21px;height:1px;margin:5px auto;background:currentColor;transition:.2s} +.nav-toggle.open span:first-child{transform:translateY(6px) rotate(45deg)} +.nav-toggle.open span:nth-child(2){opacity:0} +.nav-toggle.open span:last-child{transform:translateY(-6px) rotate(-45deg)} + +/* Hero */ +.hero{min-height:calc(100svh - var(--rf-banner-h,0px));justify-content:flex-start;padding:clamp(92px,10vh,118px) 0 18px;background:var(--ink)} +.hero::before{content:'';position:absolute;inset:0;background:linear-gradient(90deg,var(--ink) 0%,rgba(8,11,16,.96) 30%,rgba(8,11,16,.45) 60%,rgba(8,11,16,.13) 100%),linear-gradient(0deg,var(--ink) 0%,transparent 30%);z-index:1;pointer-events:none} +.hero::after{content:'';position:absolute;inset:0;background-image:linear-gradient(rgba(255,255,255,.026) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.026) 1px,transparent 1px);background-size:76px 76px;mask-image:linear-gradient(to bottom,black,transparent 80%);z-index:1;pointer-events:none} +.hero-art{position:absolute;inset:0 0 0 25%;background:url('/assets/rayforce-engine-hero.webp') center right/cover no-repeat;opacity:.72;filter:saturate(.92) contrast(1.04)} +.hero-glow{position:absolute;inset:0;background:radial-gradient(circle at 60% 55%,rgba(233,160,51,.1),transparent 35%);z-index:1} +.hero-layout{position:relative;z-index:2;display:grid;grid-template-columns:minmax(0,.82fr) minmax(560px,1.18fr);gap:clamp(40px,5vw,80px);align-content:center;align-items:end;flex:1} +.hero-copy{position:relative;max-width:780px} +.hero h1{margin:0;font-family:var(--display);font-size:clamp(3.6rem,5.2vw,6.2rem);font-weight:700;letter-spacing:-.04em;line-height:.9;text-transform:uppercase} +.hero h1 em{display:inline-block;color:var(--amber-light);font-style:normal} +.hero-lede{max-width:680px;margin:32px 0 0;color:#a4b0bc;font-size:clamp(1rem,1.25vw,1.22rem);line-height:1.72} +.hero-actions{display:flex;align-items:center;flex-wrap:wrap;gap:12px;margin-top:38px}.hero-meta{position:absolute;top:calc(100% + 11px);left:0;margin:0;color:var(--muted);font-family:var(--mono);font-size:.68rem} +.hero-actions .rf-button--primary{min-width:197px} +.hero-console{height:clamp(445px,34vw,490px);min-width:0;display:grid;grid-template-rows:48px minmax(0,1fr) auto 112px 34px;overflow:hidden;background:rgba(8,12,17,.91);border:1px solid rgba(255,255,255,.18);box-shadow:0 30px 90px rgba(0,0,0,.42);backdrop-filter:blur(14px)} +.console-bar{height:48px;display:flex;align-items:center;gap:12px;padding:0 16px;border-bottom:1px solid var(--line);color:var(--muted);font-family:var(--mono);font-size:.66rem} +.console-dots{display:flex;gap:6px}.console-dots i{display:block;width:7px;height:7px;border-radius:50%;background:#3c4650}.console-dots i:first-child{background:var(--amber)}.console-bar>a:hover{color:var(--white)} +.console-status{display:flex;align-items:center;gap:7px;margin-left:auto;color:var(--teal)}.console-status i{width:5px;height:5px;border-radius:50%;background:currentColor;box-shadow:0 0 8px currentColor} +.demo-code{min-height:0}.demo-code pre{height:100%;min-height:0;margin:0;padding:14px 20px;overflow:auto;color:#e5ebf0;font:400 .68rem/1.55 var(--mono)} +.hero-console code,.query-editor code{font:inherit;background:none;color:inherit;padding:0} +.code-muted{color:#617080}.code-key{color:var(--amber-light)}.code-string{color:var(--teal)}.code-number{color:var(--blue)} +.demo-run{padding:10px 16px 11px;border-top:1px solid var(--line);border-bottom:1px solid var(--line);background:rgba(255,255,255,.018)} +.demo-run-head{display:flex;align-items:center;justify-content:space-between;color:var(--amber-light);font:600 .62rem var(--mono);letter-spacing:.04em;text-transform:uppercase}.demo-run-head button{padding:3px 0;border:0;background:none;color:#6d7b88;font:600 .55rem var(--mono);text-transform:uppercase;cursor:pointer}.demo-run-head button:hover{color:var(--white)} +.demo-progress{height:2px;margin-top:8px;background:#26313b;overflow:hidden}.demo-progress i{display:block;width:0;height:100%;background:linear-gradient(90deg,var(--amber),var(--teal));box-shadow:0 0 10px rgba(94,234,212,.35);transition:width .65s cubic-bezier(.22,1,.36,1)} +.demo-stages{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin:8px 0 0;padding:0;list-style:none}.demo-stages li{position:relative;margin:0;padding-left:11px;color:#53616e;font:600 .49rem var(--mono);text-transform:uppercase;transition:color .25s}.demo-stages li i{position:absolute;left:0;top:3px;width:5px;height:5px;border:1px solid currentColor;border-radius:50%}.demo-stages li span,.demo-stages li small{display:block}.demo-stages li small{margin-top:2px;color:#44515d;font-size:.44rem;font-weight:400;text-transform:none}.demo-stages li.is-active{color:var(--amber-light)}.demo-stages li.is-active i{background:currentColor;box-shadow:0 0 8px currentColor}.demo-stages li.is-complete{color:var(--teal)}.demo-stages li.is-complete i{background:currentColor} +.demo-result{height:112px;overflow:hidden;visibility:hidden;opacity:0;transform:translateY(5px);transition:opacity .35s,transform .35s,visibility 0s linear .35s}.demo-result.is-visible{visibility:visible;opacity:1;transform:none;transition-delay:0s}.demo-result>div{display:grid;grid-template-columns:.75fr .72fr 1.3fr;align-items:center;min-height:22px;padding:0 16px;border-bottom:1px solid rgba(255,255,255,.06);color:#96a3af;font:400 .54rem var(--mono)}.demo-result>div>*:nth-child(n+2){text-align:right}.demo-result strong{color:var(--teal);font-weight:600}.demo-result .demo-result-head{min-height:24px;color:#52606d;font-size:.47rem;text-transform:uppercase;letter-spacing:.08em} +.demo-foot{min-height:34px;display:flex;align-items:center;justify-content:space-between;gap:16px;padding:0 16px;color:#53616e;font:400 .49rem var(--mono)}.demo-foot code{color:#85929e}.demo-foot a{color:var(--amber-light);font-weight:600}.demo-foot a:hover{color:var(--white)} +.hero-foot{position:relative;z-index:2;min-height:44px;display:grid;grid-template-columns:auto 1fr;gap:28px;align-items:center;margin-top:14px;padding-top:12px;border-top:1px solid var(--line)} +.hero-foot>p{margin:0;color:var(--muted);font:600 .58rem var(--mono);letter-spacing:.1em;text-transform:uppercase} +.hero-socials{display:flex;align-items:center;justify-content:flex-start}.hero-socials a{display:inline-flex;align-items:center;gap:9px;padding:3px 18px;border-left:1px solid var(--line);color:#8e9ba7;font:600 .6rem var(--mono);letter-spacing:.06em;text-transform:uppercase;transition:color .2s}.hero-socials a:hover{color:var(--white)}.hero-socials a svg{width:15px;height:15px;flex:0 0 auto;fill:var(--amber)} + +/* Unified engine */ +.engine{padding:140px 0;background:var(--paper);color:#12171c} +.engine::before{content:'RAYFORCE';position:absolute;right:-.04em;bottom:-.16em;color:rgba(18,23,28,.035);font:700 clamp(10rem,22vw,27rem)/1 var(--display);letter-spacing:-.06em;pointer-events:none} +.section-grid{display:grid;grid-template-columns:.8fr 1.2fr;gap:clamp(70px,11vw,180px);align-items:center} +.section-copy h2,.query h2,.capabilities h2,.production h2,.cloud h2,.open-source h2{margin:0;font:700 clamp(3rem,4.5vw,5.5rem)/.94 var(--display);letter-spacing:-.035em;text-transform:uppercase} +.section-copy>p:not(.rf-kicker),.capabilities-intro>p:not(.rf-kicker),.production-copy>p:not(.rf-kicker){max-width:560px;margin:30px 0 0;color:#53606b;font-size:1.06rem;line-height:1.75} +.engine .rf-kicker{color:#9a641b}.engine .rf-text-link{color:#8e5b17} +.pipeline{position:relative;border-top:1px solid rgba(18,23,28,.16)} +.pipeline-rail{position:absolute;top:0;left:0;width:25%;height:3px;background:var(--amber)} +.pipeline-step{min-height:112px;display:grid;grid-template-columns:52px 1fr auto;gap:24px;align-items:center;border-bottom:1px solid rgba(18,23,28,.16);transition:background .25s,padding .25s} +.pipeline-step:hover,.pipeline-step.is-active{padding:0 20px;background:#ebe5da}.pipeline-index{font:600 .67rem var(--mono);color:#9d7c4b}.pipeline-step p{margin:0 0 5px;color:#8c5c16;font:600 .62rem var(--mono);letter-spacing:.11em;text-transform:uppercase}.pipeline-step h3{margin:0;font:600 clamp(1.4rem,2vw,2rem) var(--display);text-transform:uppercase}.pipeline-step small{color:#69747d;font:600 .62rem var(--mono);text-transform:uppercase} +.engine-metrics{position:relative;z-index:1;display:grid;grid-template-columns:repeat(4,1fr);margin-top:110px;border-top:1px solid rgba(18,23,28,.18);border-bottom:1px solid rgba(18,23,28,.18)} +.engine-metrics div{padding:30px 26px;border-right:1px solid rgba(18,23,28,.18)}.engine-metrics div:first-child{padding-left:0}.engine-metrics div:last-child{border:0}.engine-metrics strong,.engine-metrics span{display:block}.engine-metrics strong{font:700 clamp(2.4rem,4vw,4.5rem)/1 var(--display)}.engine-metrics span{margin-top:8px;color:#69747d;font:600 .64rem var(--mono);text-transform:uppercase;letter-spacing:.06em} + +/* Query workbench */ +.query{padding:130px 0;background:#0b1118} +.query::before{content:'';position:absolute;inset:0;background:radial-gradient(circle at 86% 20%,rgba(94,234,212,.075),transparent 28%),linear-gradient(rgba(255,255,255,.025) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.025) 1px,transparent 1px);background-size:auto,72px 72px,72px 72px;mask-image:linear-gradient(to bottom,black,transparent 90%)} +.query-head{position:relative;display:grid;grid-template-columns:1.4fr .6fr;gap:80px;align-items:end}.query-head>p{max-width:480px;margin:0 0 8px;color:#8998a6;font-size:1rem;line-height:1.7} +.query-workbench{position:relative;margin-top:70px;border:1px solid var(--line-strong);background:#080c11;box-shadow:0 38px 120px rgba(0,0,0,.35)} +.query-tabs{height:58px;display:flex;align-items:center;padding:0 18px;border-bottom:1px solid var(--line)}.query-tabs button{height:100%;padding:0 18px;border:0;border-bottom:2px solid transparent;background:none;color:var(--muted);font:600 .68rem var(--mono);cursor:pointer}.query-tabs button:hover{color:var(--white)}.query-tabs button[aria-selected=true]{border-color:var(--amber);color:var(--amber-light)}.query-tabs>span{margin-left:auto;color:#52616f;font:400 .62rem var(--mono);text-transform:uppercase;letter-spacing:.08em} +.query-body{display:grid;grid-template-columns:1.12fr .88fr;min-height:440px}.query-editor{border-right:1px solid var(--line)}.query-editor pre{height:100%;margin:0;padding:42px;overflow:auto;color:#dce4eb;font:400 clamp(.74rem,1vw,.88rem)/1.9 var(--mono)}.query-editor pre[hidden]{display:none}.query-output{padding:34px}.output-head{display:flex;justify-content:space-between;padding-bottom:18px;border-bottom:1px solid var(--line);color:var(--muted);font:600 .6rem var(--mono);letter-spacing:.08em}.output-head span:last-child{color:var(--teal)}.output-row{display:grid;grid-template-columns:1.4fr .5fr .8fr;padding:17px 8px;border-bottom:1px solid rgba(255,255,255,.07);font:400 .7rem var(--mono)}.output-row span:last-child{text-align:right;color:var(--amber-light)}.output-label{color:#657483;font-size:.58rem;text-transform:uppercase;letter-spacing:.08em} + +/* Capabilities */ +.capabilities{padding:140px 0;background:var(--ink);color:var(--white)} +.capabilities-layout{display:grid;grid-template-columns:.7fr 1.3fr;gap:clamp(70px,10vw,170px);align-items:start}.capabilities-intro{position:sticky;top:150px}.capabilities-intro h2{font-size:clamp(3rem,4.2vw,5.2rem)}.capabilities-intro>p:not(.rf-kicker){color:#8998a6} +.capability-list{border-top:1px solid var(--line-strong)}.capability{display:grid;grid-template-columns:54px 1fr auto;gap:26px;align-items:center;min-height:150px;border-bottom:1px solid var(--line-strong);transition:background .25s,padding .25s}.capability:hover{padding:0 22px;background:rgba(255,255,255,.035)}.capability>span{color:var(--amber);font:600 .65rem var(--mono)}.capability h3{margin:0;font:600 clamp(1.8rem,2.8vw,3.2rem) var(--display);text-transform:uppercase}.capability p{max-width:680px;margin:8px 0 0;color:#82909e;font-size:.86rem;line-height:1.55}.capability i{color:var(--amber);font-style:normal;font-size:1.2rem;transition:transform .2s}.capability:hover i{transform:translate(4px,-4px)} + +/* Production users */ +.production{padding:140px 0;background:#111820} +.production::after{content:'';position:absolute;inset:0;background:radial-gradient(circle at 15% 75%,rgba(96,165,250,.08),transparent 30%);pointer-events:none} +.production-layout{position:relative;z-index:1;display:grid;grid-template-columns:.72fr 1.28fr;gap:clamp(70px,10vw,170px);align-items:center}.production-copy>p:not(.rf-kicker){color:#92a0ac}.production-copy .production-note{padding-left:16px;border-left:2px solid var(--amber);color:#667684!important;font:400 .66rem/1.6 var(--mono)!important} +.customer-stack{display:grid;gap:18px}.customer-card{display:block;padding:34px 38px;border:1px solid var(--line-strong);background:rgba(8,11,16,.46);transition:transform .25s,border-color .25s,background .25s}.customer-card:hover{transform:translateX(-8px);border-color:rgba(233,160,51,.48);background:rgba(8,11,16,.72)}.customer-top{display:flex;justify-content:space-between;color:#687886;font:600 .58rem var(--mono);letter-spacing:.1em}.customer-top i{color:var(--amber);font-style:normal;font-size:1rem}.customer-wordmark{display:block;margin-top:28px;color:var(--white);font:700 clamp(2.8rem,5vw,5.8rem)/1 var(--display);letter-spacing:.12em}.customer-wordmark--fort{font-size:clamp(2.2rem,4vw,4.6rem);letter-spacing:.04em}.customer-card h3{max-width:640px;margin:22px 0 0;font:600 clamp(1.3rem,2vw,2rem)/1.15 var(--display);text-transform:uppercase}.customer-card p{max-width:650px;margin:13px 0 0;color:#83919e;font-size:.82rem;line-height:1.65} + +/* Cloud */ +.cloud{padding:140px 0;background:#0a1017} +.cloud::before{content:'';position:absolute;inset:0;background:radial-gradient(circle at 78% 50%,rgba(233,160,51,.16),transparent 17%),radial-gradient(circle at 78% 50%,rgba(94,234,212,.08),transparent 36%);pointer-events:none} +.cloud-orbit{position:absolute;left:78%;top:50%;border:1px solid rgba(233,160,51,.14);border-radius:50%;transform:translate(-50%,-50%);pointer-events:none}.cloud-orbit--one{width:520px;height:520px}.cloud-orbit--two{width:780px;height:780px;border-color:rgba(94,234,212,.08)} +.cloud-layout{position:relative;z-index:1;display:grid;grid-template-columns:1fr .82fr;gap:clamp(70px,10vw,160px);align-items:center}.cloud h2 em{color:var(--amber-light);font-style:normal}.cloud-copy>p:not(.rf-kicker){max-width:720px;margin:30px 0 0;color:#91a0ad;font-size:1.05rem;line-height:1.75}.cloud-actions{display:flex;align-items:center;gap:20px;margin-top:36px}.cloud-actions>span{color:#5e6d7a;font:400 .62rem var(--mono)} +.cloud-panel{padding:24px;border:1px solid rgba(233,160,51,.3);background:rgba(8,11,16,.84);box-shadow:0 40px 120px rgba(0,0,0,.45),inset 0 0 80px rgba(233,160,51,.03);backdrop-filter:blur(14px)}.cloud-panel-head{display:flex;justify-content:space-between;padding-bottom:20px;border-bottom:1px solid var(--line);color:#7e8d99;font:600 .63rem var(--mono);letter-spacing:.08em}.cloud-panel-head b{color:var(--amber)}.cloud-region{display:grid;grid-template-columns:1fr 1fr auto;align-items:end;padding:25px 0}.cloud-region p,.cloud-region strong,.cloud-region span{margin:0}.cloud-region p{color:#71808d;font:400 .64rem var(--mono)}.cloud-region strong{font:600 1.5rem var(--display);text-transform:uppercase}.cloud-region span{color:var(--teal);font:600 .6rem var(--mono)} +.cloud-chart{height:190px;display:flex;align-items:end;gap:8px;padding:24px 12px 0;border:1px solid var(--line);background:linear-gradient(to top,rgba(233,160,51,.04),transparent),repeating-linear-gradient(to top,transparent 0,transparent 46px,rgba(255,255,255,.045) 47px)}.cloud-chart i{flex:1;height:var(--h);background:linear-gradient(to top,rgba(233,160,51,.18),var(--amber));transform-origin:bottom;animation:bars 1.2s cubic-bezier(.22,1,.36,1) both}.cloud-chart i:nth-child(2n){background:linear-gradient(to top,rgba(94,234,212,.12),var(--teal))}@keyframes bars{from{transform:scaleY(0)}} +.cloud-stats{display:grid;grid-template-columns:1fr 1fr;border:1px solid var(--line);border-top:0}.cloud-stats div{padding:18px;border-right:1px solid var(--line)}.cloud-stats div:last-child{border:0}.cloud-stats span,.cloud-stats strong{display:block}.cloud-stats span{color:#647380;font:600 .56rem var(--mono);letter-spacing:.08em}.cloud-stats strong{margin-top:5px;font:600 1.2rem var(--display)} + +/* Open source and footer */ +.open-source{min-height:90svh;padding:140px 0;text-align:center;background:var(--paper);color:#14191e}.open-source::before{content:'';position:absolute;inset:0;background-image:linear-gradient(rgba(20,25,30,.045) 1px,transparent 1px),linear-gradient(90deg,rgba(20,25,30,.045) 1px,transparent 1px);background-size:72px 72px;mask-image:radial-gradient(circle,black,transparent 72%)} +.open-layout{position:relative}.open-source .rf-kicker{justify-content:center;color:#966116}.open-source h2{font-size:clamp(4rem,6.5vw,7.8rem)}.open-source>div>p:not(.rf-kicker){max-width:720px;margin:34px auto 0;color:#5c6771;font-size:1.08rem;line-height:1.75}.open-actions{display:flex;justify-content:center;gap:12px;margin-top:38px}.open-source .rf-button--quiet{border-color:rgba(20,25,30,.26);color:#252c32}.github-mark{fill:currentColor!important;stroke:none!important}.open-stats{display:grid;grid-template-columns:repeat(3,1fr);max-width:900px;margin:80px auto 0;border-top:1px solid rgba(20,25,30,.2);border-bottom:1px solid rgba(20,25,30,.2)}.open-stats div{padding:25px;border-right:1px solid rgba(20,25,30,.2)}.open-stats div:last-child{border:0}.open-stats strong,.open-stats span{display:block}.open-stats strong{font:700 2.1rem var(--display);text-transform:uppercase}.open-stats span{margin-top:6px;color:#6d777f;font:600 .6rem var(--mono);text-transform:uppercase} +.footer{padding:80px 0 24px;background:#06090d;border-top:1px solid var(--line);color:var(--white)}.footer-grid{display:grid;grid-template-columns:1.8fr repeat(3,1fr);gap:70px}.footer-brand img{display:block;width:174px;height:auto}.footer-brand p{max-width:310px;margin:20px 0 0;color:#697785;font-size:.78rem;line-height:1.65}.footer-column{display:flex;flex-direction:column;gap:13px}.footer-column>span{margin-bottom:6px;color:var(--amber);font:600 .6rem var(--mono);letter-spacing:.12em}.footer-column a{color:#8794a0;font-size:.76rem;transition:color .2s}.footer-column a:hover{color:var(--white)}.footer-bottom{display:flex;justify-content:space-between;margin-top:70px;padding-top:20px;border-top:1px solid var(--line);color:#485561;font:400 .58rem var(--mono)} + +/* About page */ +.promo-frame{display:block;width:100%;height:100svh;border:0;background:var(--ink)} +.about-content{padding:160px 0 120px;background:var(--ink);color:var(--white)}.about-content .container{width:min(calc(100% - 80px),1000px);margin:auto}.about-content .md-typeset{font-family:var(--sans);font-size:1rem}.about-content h1,.about-content h2,.about-content h3{font-family:var(--display);text-transform:uppercase}.about-content h1{font-size:clamp(3.4rem,6vw,6.2rem);line-height:.92;color:var(--white)}.about-content h2{margin-top:80px;font-size:clamp(2.4rem,4vw,4.2rem);border-color:var(--line)}.about-content p,.about-content li{color:#98a5b0;line-height:1.75}.about-content a{color:var(--amber-light)} + +@media (min-width:901px) and (max-width:1300px){ + .hero-actions{gap:10px}.hero-actions .rf-button{gap:9px;padding:12px 18px;font-size:.72rem} +} + +@media (max-width:1100px){ + .rf-container{width:min(calc(100% - 48px),var(--max))} + .hero-layout{grid-template-columns:minmax(0,.78fr) minmax(500px,1.22fr);gap:36px}.hero h1{font-size:clamp(3.4rem,5.7vw,5.2rem)}.demo-code pre{font-size:.66rem;padding:20px} + .section-grid,.capabilities-layout,.production-layout,.cloud-layout{gap:70px}.footer-grid{gap:35px} +} + +@media (max-width:900px){ + .rf-screen{min-height:auto}.nav{top:var(--rf-banner-h,0px);height:70px}.nav-inner{padding:0}.nav-brand,.nav-toggle{position:relative;z-index:2}.nav-brand img{width:116px}.nav-toggle{display:block;cursor:pointer}.nav-links{display:none;position:fixed;z-index:1;top:70px;right:0;left:0;height:calc(100svh - var(--rf-banner-h,0px) - 70px);flex-direction:column;align-items:stretch;justify-content:center;padding:clamp(28px,8vw,72px);background:rgba(8,11,16,.985);border-top:1px solid var(--line)}.nav-links.open{display:flex}.nav-links>a{padding:12px 0;border-bottom:1px solid var(--line);background:none!important;color:#a8b3bd;font:600 clamp(1.9rem,7vw,3.4rem)/1 var(--display);letter-spacing:-.02em;text-transform:uppercase}.nav-links>a:hover,.nav-links>a.active{color:var(--amber-light)}.nav-github{width:100%;flex:0 0 auto;justify-content:flex-start;margin:12px 0 0;border:0!important;font-family:var(--display)!important}.nav-github svg{width:24px;height:24px}.nav-open{overflow:hidden} + .hero{min-height:100svh;padding:150px 0 38px}.hero::before{background:linear-gradient(0deg,var(--ink) 0%,rgba(8,11,16,.88) 66%,rgba(8,11,16,.55) 100%)}.hero-art{inset:0;opacity:.28;background-position:center}.hero-layout{grid-template-columns:1fr;align-content:normal;align-items:start}.hero-copy{max-width:700px}.hero-meta{position:static;margin:13px 0 0}.hero-console{width:100%;height:auto;display:block;overflow:visible;margin-top:35px}.hero-foot{grid-template-columns:1fr;align-items:start;gap:12px;margin-top:38px}.hero-socials{justify-content:flex-start;flex-wrap:wrap;gap:8px 0}.hero-socials a:first-child{padding-left:0;border-left:0} + .engine,.query,.capabilities,.production,.cloud,.open-source{padding:110px 0}.section-grid,.query-head,.capabilities-layout,.production-layout,.cloud-layout{grid-template-columns:1fr}.capabilities-intro{position:static}.engine-metrics{grid-template-columns:1fr 1fr;margin-top:80px}.engine-metrics div:nth-child(2){border-right:0}.engine-metrics div:nth-child(-n+2){border-bottom:1px solid rgba(18,23,28,.18)}.engine-metrics div{padding-left:22px!important}.query-head{gap:25px}.query-body{grid-template-columns:1fr}.query-editor{border-right:0;border-bottom:1px solid var(--line)}.query-output{padding:25px}.production-layout,.cloud-layout{gap:60px}.cloud-orbit{left:80%;opacity:.6}.footer-grid{grid-template-columns:1.5fr 1fr 1fr}.footer-column:last-child{grid-column:2/4}.footer-bottom{gap:20px} +} + +@media (max-width:600px){ + .rf-container{width:calc(100% - 32px)} + .rf-kicker{font-size:.61rem}.rf-kicker>span{width:22px}.hero{padding-top:138px}.hero h1{font-size:clamp(2.9rem,13vw,4.3rem)}.hero-lede{font-size:.94rem}.hero-actions,.open-actions,.cloud-actions{align-items:stretch;flex-direction:column}.rf-button{width:100%}.hero-foot{margin-top:34px}.hero-socials a{padding:3px 10px;font-size:.54rem} + .demo-code pre{min-height:190px;padding:18px 14px;font-size:.57rem}.demo-run{padding:14px}.demo-stages{gap:4px}.demo-stages li{padding-left:9px;font-size:.45rem}.demo-stages li small{font-size:.4rem}.demo-result>div{grid-template-columns:.65fr .65fr 1.35fr;padding:0 13px;font-size:.5rem}.demo-foot{align-items:flex-start;flex-direction:column;gap:4px;padding:10px 14px} + .engine,.query,.capabilities,.production,.cloud,.open-source{padding:88px 0}.section-copy h2,.query h2,.capabilities h2,.production h2,.cloud h2{font-size:clamp(2.7rem,12vw,4rem)}.open-source h2{font-size:clamp(3.4rem,14vw,5rem)} + .pipeline-step{grid-template-columns:34px 1fr;gap:12px;padding:16px 0}.pipeline-step small{display:none}.pipeline-step:hover,.pipeline-step.is-active{padding:16px 10px}.pipeline-step h3{font-size:1.3rem}.engine-metrics{margin-top:60px}.engine-metrics strong{font-size:2.6rem}.engine-metrics span{font-size:.54rem} + .query-workbench{margin-top:45px}.query-tabs{overflow:auto}.query-tabs button{padding:0 12px}.query-tabs>span{display:none}.query-editor pre{min-height:380px;padding:24px 18px;font-size:.66rem}.query-output{padding:18px 12px}.output-row{grid-template-columns:1.2fr .45fr .8fr;font-size:.6rem;padding:14px 4px} + .capability{grid-template-columns:30px 1fr auto;gap:12px;min-height:130px}.capability h3{font-size:1.8rem}.capability p{font-size:.74rem}.capability:hover{padding:0 8px} + .customer-card{padding:26px 22px}.customer-wordmark{font-size:3.2rem}.customer-wordmark--fort{font-size:2.5rem}.customer-top span{max-width:240px}.cloud-panel{padding:16px}.cloud-chart{height:145px;gap:5px}.cloud-actions>span{text-align:center} + .open-stats{grid-template-columns:1fr;margin-top:58px}.open-stats div{border-right:0;border-bottom:1px solid rgba(20,25,30,.2)}.open-stats div:last-child{border-bottom:0}.footer{padding-top:60px}.footer-grid{grid-template-columns:1fr 1fr}.footer-brand{grid-column:1/-1}.footer-column:last-child{grid-column:auto}.footer-bottom{flex-direction:column;margin-top:50px}.about-content .container{width:calc(100% - 32px)} +} + +@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}.has-reveal .reveal,.reveal{opacity:1;transform:none;transition:none}.cloud-chart i{animation:none}.rf-button,.capability,.customer-card{transition:none}} diff --git a/docs/assets/rayforce-engine-hero.webp b/docs/assets/rayforce-engine-hero.webp new file mode 100644 index 000000000..4e9299e2b Binary files /dev/null and b/docs/assets/rayforce-engine-hero.webp differ diff --git a/docs/docs/c-api/core.md b/docs/docs/c-api/core.md index e1511b963..846b1faf5 100644 --- a/docs/docs/c-api/core.md +++ b/docs/docs/c-api/core.md @@ -44,14 +44,13 @@ void ray_sym_destroy(void); ```c #include -int main(void) { - ray_heap_init(); - ray_sym_init(); +int main(int argc, char** argv) { + ray_runtime_t* runtime = ray_runtime_create(argc, argv); + if (!runtime) return 1; /* ... use Rayforce API ... */ - ray_sym_destroy(); - ray_heap_destroy(); + ray_runtime_destroy(runtime); return 0; } ``` @@ -196,7 +195,7 @@ ray_t* ray_date(int64_t val); ### ray_time -Creates a time atom. The value is nanoseconds since midnight. +Creates a time atom. The value is milliseconds since midnight. ```c ray_t* ray_time(int64_t val); @@ -489,7 +488,7 @@ Macro. Tests whether a `ray_t*` is an error object. Safe to call on `NULL`. Retu ray_t* result = ray_execute(g, root); if (RAY_IS_ERR(result)) { printf("Error: %s\n", ray_err_code(result)); - ray_release(result); + ray_error_free(result); return 1; } ``` diff --git a/docs/docs/c-api/dag.md b/docs/docs/c-api/dag.md index 473340045..5d6d61804 100644 --- a/docs/docs/c-api/dag.md +++ b/docs/docs/c-api/dag.md @@ -372,7 +372,7 @@ ray_t* ray_execute(ray_graph_t* g, ray_op_t* root); ray_t* result = ray_execute(g, ray_optimize(g, root)); if (RAY_IS_ERR(result)) { /* handle error */ - ray_release(result); + ray_error_free(result); } ``` diff --git a/docs/docs/namespaces/csv.md b/docs/docs/namespaces/csv.md index 9aaf5d904..29b00307b 100644 --- a/docs/docs/namespaces/csv.md +++ b/docs/docs/namespaces/csv.md @@ -22,7 +22,7 @@ Signatures: - `(.csv.read [types] "path")` — types is a SYM vector naming `B8`, `I64`, `F64`, `STR`, `SYMBOL`, `F32`, `DATE`, `TIME`, `TIMESTAMP`, `GUID` (case-insensitive). One entry per column. - `(.csv.read [names] [types] "path")` — also override the column names. With explicit names the input is assumed to have **no header row**. -Returns: a `table`. Empty fields and the empty string are read as null with `RAY_ATTR_HAS_NULLS` set on the column. +Returns: a `table`. Empty fields use the selected type's missing-input behavior. Sentinel-nullable columns record nulls with `RAY_ATTR_HAS_NULLS`; `SYM`/`STR` fields collapse to ordinary empty values and those columns do not acquire `RAY_ATTR_HAS_NULLS` from them. Errors: `type` (bad arg), `domain` (path is null / too long, or zero columns), `io` (open/read failure), `limit` (>256 columns). diff --git a/docs/docs/namespaces/sys.md b/docs/docs/namespaces/sys.md index c8b3ce7a3..2e50c6ef5 100644 --- a/docs/docs/namespaces/sys.md +++ b/docs/docs/namespaces/sys.md @@ -31,7 +31,7 @@ Signature: `(.sys.args)`. Returns the process's command-line arguments as a dict |---|---|---|---| | `file` | str | `-f` / positional | Script path; empty if none. | | `port` | i64 | `-p` | IPC listen port; `0` if unset. | -| `cores` | i64 | `-c` | Worker-pool size; `0` = auto. | +| `cores` | i64 | `-c` | Total execution cores, including the main thread; `0` = auto. | | `timeit` | bool | `-t` | Profiler enabled at startup. | | `querylog` | bool | `-Q` | Query-statistics logging enabled at startup. | | `interactive` | bool | `-i` | Force the REPL after a script. | diff --git a/docs/javascripts/ai-assist-widget.js b/docs/javascripts/ai-assist-widget.js index a1c1f016c..d4192f247 100644 --- a/docs/javascripts/ai-assist-widget.js +++ b/docs/javascripts/ai-assist-widget.js @@ -129,21 +129,24 @@ } function buildWidget() { - const launcher = document.createElement('button'); - launcher.id = 'rfai-launcher'; - launcher.className = 'rfai-launcher'; - launcher.type = 'button'; - launcher.setAttribute('aria-haspopup', 'dialog'); - launcher.setAttribute('aria-controls', 'rfai-dialog'); - launcher.setAttribute('aria-expanded', 'false'); - launcher.innerHTML = ` - - Ask AI`; + let launcher = document.querySelector('#rfai-launcher'); + if (!launcher) { + launcher = document.createElement('button'); + launcher.id = 'rfai-launcher'; + launcher.className = 'rfai-launcher'; + launcher.type = 'button'; + launcher.setAttribute('aria-haspopup', 'dialog'); + launcher.setAttribute('aria-controls', 'rfai-dialog'); + launcher.setAttribute('aria-expanded', 'false'); + launcher.innerHTML = ` + + Ask AI`; + } const dialog = document.createElement('section'); dialog.id = 'rfai-dialog'; @@ -178,7 +181,7 @@ `; - document.body.appendChild(launcher); + if (!launcher.isConnected) document.body.appendChild(launcher); document.body.appendChild(dialog); return { launcher, diff --git a/docs/javascripts/landing.js b/docs/javascripts/landing.js index 55f8af332..a76e9048c 100644 --- a/docs/javascripts/landing.js +++ b/docs/javascripts/landing.js @@ -1,356 +1,145 @@ document.addEventListener('DOMContentLoaded', () => { 'use strict'; - // ── Scroll-triggered fade-in ────────────────── - // Cards have `opacity: 0; transform: translateY(...)` in CSS so they - // can fade-in when they scroll into view. The catch: anything already - // in the viewport at first paint *also* runs that animation, which - // reads as the page "stretching itself in" on hard reload. Snap those - // to visible instantly; only animate elements that scroll into view - // *after* the initial paint. - const animatedSel = '.feature-card, .why-card, .why-card-small, .usecase-item, .hero-stat'; - const animatedEls = document.querySelectorAll(animatedSel); - if (animatedEls.length > 0) { - const inViewportNow = (el) => { - const r = el.getBoundingClientRect(); - return r.bottom > 0 && r.top < (window.innerHeight || document.documentElement.clientHeight); - }; + const nav = document.querySelector('.nav'); - const snap = (el) => { - // Disable transition for one frame so adding .visible doesn't animate. - const prev = el.style.transition; - el.style.transition = 'none'; - el.classList.add('visible'); - // Force the browser to commit the no-transition paint, then restore so - // future state changes (hover etc.) still animate. - // eslint-disable-next-line no-unused-expressions - el.offsetHeight; - requestAnimationFrame(() => { el.style.transition = prev; }); - }; + const sections = Array.from(document.querySelectorAll('section[id]')); + const sectionLinks = Array.from(document.querySelectorAll('.nav-links a[href*="#"]')); + const updateNav = () => { + if (nav) nav.classList.toggle('nav-scrolled', window.scrollY > 32); + const current = sections.findLast + ? sections.findLast((section) => section.offsetTop <= window.scrollY + 180) + : sections.slice().reverse().find((section) => section.offsetTop <= window.scrollY + 180); + sectionLinks.forEach((link) => { + link.classList.toggle('active', Boolean(current && link.hash === '#' + current.id)); + }); + }; + window.addEventListener('scroll', updateNav, { passive: true }); + updateNav(); - const observer = new IntersectionObserver((entries) => { + const reduceMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; + const reveals = document.querySelectorAll('.reveal'); + if (reduceMotion || !('IntersectionObserver' in window)) { + reveals.forEach((el) => el.classList.add('is-visible')); + } else { + const revealObserver = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (!entry.isIntersecting) return; - const parent = entry.target.parentElement; - if (parent) { - const siblings = parent.querySelectorAll(animatedSel); - const index = Array.prototype.indexOf.call(siblings, entry.target); - if (index > 0) { - entry.target.style.transitionDelay = (index * 0.12) + 's'; - } - } - entry.target.classList.add('visible'); - observer.unobserve(entry.target); + entry.target.classList.add('is-visible'); + revealObserver.unobserve(entry.target); }); - }, { threshold: 0.15 }); - - animatedEls.forEach((el) => { - if (inViewportNow(el)) { - snap(el); - } else { - observer.observe(el); - } - }); + }, { threshold: 0.14, rootMargin: '0px 0px -6% 0px' }); + reveals.forEach((el) => revealObserver.observe(el)); } - // ── Mobile nav toggle ───────────────────────── - const navToggle = document.querySelector('.nav-toggle'); - const navLinks = document.querySelector('.nav-links'); - if (navToggle && navLinks) { - navToggle.addEventListener('click', () => { - const isOpen = navLinks.classList.toggle('open'); - navToggle.classList.toggle('open'); - navToggle.setAttribute('aria-expanded', String(isOpen)); - }); - navLinks.querySelectorAll('a').forEach((link) => { - link.addEventListener('click', () => { - navLinks.classList.remove('open'); - navToggle.classList.remove('open'); - navToggle.setAttribute('aria-expanded', 'false'); + document.querySelectorAll('[data-code-tabs]').forEach((tabs) => { + const buttons = tabs.querySelectorAll('[data-code-tab]'); + const panels = tabs.querySelectorAll('[data-code-panel]'); + buttons.forEach((button) => { + button.addEventListener('click', () => { + const selected = button.dataset.codeTab; + buttons.forEach((item) => item.setAttribute('aria-selected', String(item === button))); + panels.forEach((panel) => { panel.hidden = panel.dataset.codePanel !== selected; }); }); }); - } - - // ── Nav shadow + active link ────────────────── - const nav = document.querySelector('.nav'); - const sections = document.querySelectorAll('section[id]'); - const navAnchors = document.querySelectorAll('.nav-links a[href^="#"]'); - function updateNav() { - if (nav) { - nav.classList.toggle('nav-scrolled', window.scrollY > 50); - } - let currentId = ''; - const scrollY = window.scrollY + 120; - sections.forEach((section) => { - const top = section.offsetTop; - const height = section.offsetHeight; - if (scrollY >= top && scrollY < top + height) { - currentId = section.getAttribute('id'); - } - }); - navAnchors.forEach((a) => { - a.classList.toggle('active', a.getAttribute('href') === '#' + currentId); - }); - } - window.addEventListener('scroll', updateNav, { passive: true }); - updateNav(); - - // ── Scroll-to-top ───────────────────────────── - const scrollTopBtn = document.querySelector('.scroll-top'); - if (scrollTopBtn) { - window.addEventListener('scroll', () => { - scrollTopBtn.classList.toggle('visible', window.scrollY > 600); - }, { passive: true }); - scrollTopBtn.addEventListener('click', () => { - window.scrollTo({ top: 0, behavior: 'smooth' }); - }); - } - - // ── Terminal typing animation ───────────────── - // Output generated by: python3 -c 'json.dumps(line)' on actual ./rayforce pipe - const TERMINAL_FRAMES = [ - { type: 'cmd', text: '$ ./rayforce' }, - { type: 'wait', ms: 300 }, - { type: 'cmd', text: '\u2023 (set t (table [Symbol Side Qty]', prompt: true }, - { type: 'cmd', text: ' (list [AAPL GOOG MSFT AAPL GOOG]' }, - { type: 'cmd', text: ' [Buy Sell Buy Sell Buy]' }, - { type: 'cmd', text: ' [100 200 150 300 250])))' }, - { type: 'wait', ms: 400 }, - { type: 'out', lines: [ - '\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510', - '\u2502 Symbol \u2502 Side \u2502 Qty \u2502', - '\u2502 sym \u2502 sym \u2502 i64 \u2502', - '\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524', - '\u2502 AAPL \u2502 Buy \u2502 100 \u2502', - '\u2502 GOOG \u2502 Sell \u2502 200 \u2502', - '\u2502 MSFT \u2502 Buy \u2502 150 \u2502', - '\u2502 AAPL \u2502 Sell \u2502 300 \u2502', - '\u2502 GOOG \u2502 Buy \u2502 250 \u2502', - '\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524', - '\u2502 5 rows (5 shown) 3 columns (3 shown)\u2502', - '\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518', - '', - ]}, - { type: 'wait', ms: 1200 }, - { type: 'cmd', text: '\u2023 (select {from:t by: Symbol Qty: (sum Qty)})', prompt: true }, - { type: 'wait', ms: 400 }, - { type: 'out', lines: [ - '\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510', - '\u2502 Symbol \u2502 Qty \u2502', - '\u2502 sym \u2502 i64 \u2502', - '\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524', - '\u2502 AAPL \u2502 400 \u2502', - '\u2502 GOOG \u2502 450 \u2502', - '\u2502 MSFT \u2502 150 \u2502', - '\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524', - '\u2502 3 rows (3 shown) 2 columns (2 shown)\u2502', - '\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518', - '', - ]}, - { type: 'wait', ms: 1200 }, - { type: 'cmd', text: "\u2023 (pivot t 'Symbol 'Side 'Qty sum)", prompt: true }, - { type: 'wait', ms: 400 }, - { type: 'out', lines: [ - '\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510', - '\u2502 Symbol \u2502 Buy \u2502 Sell \u2502', - '\u2502 sym \u2502 i64 \u2502 i64 \u2502', - '\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524', - '\u2502 AAPL \u2502 100 \u2502 300 \u2502', - '\u2502 GOOG \u2502 250 \u2502 200 \u2502', - '\u2502 MSFT \u2502 150 \u2502 0 \u2502', - '\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524', - '\u2502 3 rows (3 shown) 3 columns (3 shown)\u2502', - '\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518', - '', - ]}, - { type: 'wait', ms: 4000 }, - { type: 'clear' }, - ]; - - class TerminalAnimation { - constructor(el, frames) { - this.el = el; - this.frames = frames; - this.running = false; - } - - async start() { - if (this.running) return; - this.running = true; - while (this.running) { - this.el.innerHTML = ''; - for (const frame of this.frames) { - if (!this.running) return; - switch (frame.type) { - case 'cmd': await this.typeCommand(frame.text, frame.prompt); break; - case 'out': await this.showOutput(frame.lines); break; - case 'wait': await this.wait(frame.ms); break; - case 'clear': - await this.wait(300); - this.el.style.opacity = '0'; - await this.wait(300); - this.el.innerHTML = ''; - this.el.style.opacity = '1'; - break; - } - } - } - } - - stop() { - this.running = false; - } - - async typeCommand(text, prompt) { - const line = document.createElement('div'); - line.className = 'term-line term-cmd'; - this.el.appendChild(line); - - const cursor = document.createElement('span'); - cursor.className = 'term-cursor'; - cursor.textContent = '\u2588'; - - for (let i = 0; i < text.length; i++) { - if (!this.running) return; - // Render prompt character (‣) in green - if (prompt && text[0] === '\u2023') { - line.innerHTML = '\u2023' + - this.escapeHtml(text.slice(1, i + 1)); - } else { - line.textContent = text.slice(0, i + 1); - } - line.appendChild(cursor); - this.scrollToBottom(); - await this.wait(25 + Math.random() * 35); - } + }); + + document.querySelectorAll('[data-live-demo]').forEach((demo) => { + const label = demo.querySelector('[data-demo-label]'); + const progress = demo.querySelector('[data-demo-progress]'); + const progressTrack = demo.querySelector('[data-demo-progress-track]'); + const result = demo.querySelector('[data-demo-result]'); + const replay = demo.querySelector('[data-demo-replay]'); + const stages = Array.from(demo.querySelectorAll('[data-demo-stage]')); + const frames = [ + { stage: 'load', label: 'Loading 100,000 rows', progress: 16, delay: 1050 }, + { stage: 'optimize', label: 'Rewriting the lazy DAG', progress: 40, delay: 1250 }, + { stage: 'filter', label: 'Filtering qty > 100 · 60,000 match', progress: 68, delay: 1200 }, + { stage: 'group', label: 'Grouping in parallel by symbol', progress: 90, delay: 1150 }, + { stage: 'result', label: 'Complete · 100,000 rows → 4 groups', progress: 100, delay: 3800 } + ]; + let frameIndex = 0; + let timer = 0; + let visible = true; + + const paint = (frame) => { + label.textContent = frame.label; + progress.style.width = frame.progress + '%'; + progressTrack.setAttribute('aria-valuenow', String(frame.progress)); + stages.forEach((stage, index) => { + const activeIndex = Math.min(frameIndex, stages.length - 1); + stage.classList.toggle('is-active', frame.stage !== 'result' && index === activeIndex); + stage.classList.toggle('is-complete', frame.stage === 'result' || index < activeIndex); + }); + result.classList.toggle('is-visible', frame.stage === 'result'); + }; - cursor.remove(); - await this.wait(120); - } + const advance = () => { + if (!visible) return; + paint(frames[frameIndex]); + timer = window.setTimeout(() => { + frameIndex = (frameIndex + 1) % frames.length; + advance(); + }, frames[frameIndex].delay); + }; - escapeHtml(s) { - return s.replace(/&/g, '&').replace(//g, '>'); - } + const restart = () => { + window.clearTimeout(timer); + frameIndex = 0; + result.classList.remove('is-visible'); + advance(); + }; - async showOutput(lines) { - for (const html of lines) { - if (!this.running) return; - const line = document.createElement('div'); - line.className = 'term-line term-out'; - line.innerHTML = html; - this.el.appendChild(line); - this.scrollToBottom(); - await this.wait(35); - } - } + replay.addEventListener('click', restart); - scrollToBottom() { - this.el.scrollTop = this.el.scrollHeight; + if (reduceMotion) { + frameIndex = frames.length - 1; + paint(frames[frameIndex]); + return; } - wait(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); + if ('IntersectionObserver' in window) { + const demoObserver = new IntersectionObserver(([entry]) => { + visible = entry.isIntersecting; + window.clearTimeout(timer); + if (visible) advance(); + }, { threshold: 0.15 }); + visible = false; + demoObserver.observe(demo); + } else { + advance(); } - } - - // Start terminal when scrolled into view - const terminalOutput = document.getElementById('terminal-output'); - if (terminalOutput) { - const terminal = new TerminalAnimation(terminalOutput, TERMINAL_FRAMES); - const termObserver = new IntersectionObserver((entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - terminal.start(); - termObserver.unobserve(entry.target); - } - }); - }, { threshold: 0.3 }); - termObserver.observe(terminalOutput); - } + }); }); -/* GitHub widget: fetch live stars/forks via ungh.cc (CDN-cached proxy - * without GitHub's 60/hour unauthenticated rate limit) with a 1-hour - * localStorage cache so repeat visits don't refetch. Falls back silently - * if every source is unreachable. */ -(function () { +(function loadGitHubStats() { const targets = document.querySelectorAll('[data-gh-stat]'); - if (targets.length === 0) return; - - const REPO = 'RayforceDB/rayforce'; - const CACHE_KEY = 'gh-stats:' + REPO; - const CACHE_TTL = 60 * 60 * 1000; /* 1 hour */ - - function fmt(n) { - if (typeof n !== 'number' || isNaN(n)) return '—'; - if (n >= 10000) return (n / 1000).toFixed(1).replace(/\.0$/, '') + 'k'; - if (n >= 1000) return (n / 1000).toFixed(1) + 'k'; - return String(n); - } - - function animateCount(el, target, duration) { - if (typeof target !== 'number' || isNaN(target)) return; - const reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; - if (reduce) { el.textContent = fmt(target); return; } - const start = performance.now(); - function tick(now) { - const t = Math.min(1, (now - start) / duration); - const eased = 1 - Math.pow(1 - t, 3); /* easeOutCubic */ - el.textContent = fmt(Math.round(target * eased)); - if (t < 1) requestAnimationFrame(tick); - else el.textContent = fmt(target); + if (!targets.length) return; + + const repo = 'RayforceDB/rayforce'; + const cacheKey = 'rayforce:github-stats'; + const format = (value) => { + if (typeof value !== 'number') return '—'; + return value >= 1000 ? (value / 1000).toFixed(value >= 10000 ? 0 : 1) + 'k' : String(value); + }; + const paint = (data) => targets.forEach((el) => { + el.textContent = format(el.dataset.ghStat === 'forks' ? data.forks : data.stars); + }); + + try { + const cached = JSON.parse(localStorage.getItem(cacheKey)); + if (cached && Date.now() - cached.savedAt < 3600000) { + paint(cached); + return; } - requestAnimationFrame(tick); - } - - function paint(stars, forks) { - targets.forEach(el => { - const which = el.getAttribute('data-gh-stat'); - const target = which === 'stars' ? stars - : which === 'forks' ? forks - : NaN; - animateCount(el, target, 900); - }); - } - - function readCache() { - try { - const raw = localStorage.getItem(CACHE_KEY); - if (!raw) return null; - const v = JSON.parse(raw); - if (!v || (Date.now() - v.t) > CACHE_TTL) return null; - return v; - } catch (e) { return null; } - } - - function writeCache(stars, forks) { - try { localStorage.setItem(CACHE_KEY, JSON.stringify({ t: Date.now(), stars, forks })); } catch (e) {} - } - - /* If we have a fresh cached value, paint it instantly without a network roundtrip. */ - const cached = readCache(); - if (cached) { paint(cached.stars, cached.forks); return; } - - /* Primary: ungh.cc (CDN-cached, no rate limit). Response shape: { repo: { stars, forks, ... } } */ - fetch('https://ungh.cc/repos/' + REPO) - .then(r => r.ok ? r.json() : Promise.reject(new Error('ungh ' + r.status))) - .then(d => { - const stars = d && d.repo && d.repo.stars; - const forks = d && d.repo && d.repo.forks; - if (typeof stars !== 'number' || typeof forks !== 'number') throw new Error('ungh shape'); - writeCache(stars, forks); - paint(stars, forks); - }) - .catch(() => { - /* Fallback: direct GitHub API. Often 403s on busy IPs but free when it works. */ - return fetch('https://api.github.com/repos/' + REPO, { headers: { Accept: 'application/vnd.github+json' } }) - .then(r => r.ok ? r.json() : Promise.reject(new Error('gh ' + r.status))) - .then(d => { - writeCache(d.stargazers_count, d.forks_count); - paint(d.stargazers_count, d.forks_count); - }); + } catch (_) {} + + fetch('https://ungh.cc/repos/' + repo) + .then((response) => response.ok ? response.json() : Promise.reject(new Error('GitHub stats unavailable'))) + .then((data) => { + const stats = { stars: data.repo.stars, forks: data.repo.forks, savedAt: Date.now() }; + paint(stats); + try { localStorage.setItem(cacheKey, JSON.stringify(stats)); } catch (_) {} }) - .catch(() => { - /* Leave the static "—" placeholders. Users still get a working link. */ - }); + .catch(() => {}); })(); diff --git a/docs/javascripts/main.js b/docs/javascripts/main.js index 75eb8f2a8..091f34014 100644 --- a/docs/javascripts/main.js +++ b/docs/javascripts/main.js @@ -16,17 +16,32 @@ document.addEventListener('DOMContentLoaded', () => { const navToggle = document.querySelector('.nav-toggle'); const navLinks = document.querySelector('.nav-links'); if (navToggle && navLinks) { + const closeNavigation = () => { + navLinks.classList.remove('open'); + navToggle.classList.remove('open'); + navToggle.setAttribute('aria-expanded', 'false'); + document.body.classList.remove('nav-open'); + }; + navToggle.addEventListener('click', () => { + const drawer = document.querySelector('#__drawer'); + if (drawer && drawer.checked) { + drawer.checked = false; + drawer.dispatchEvent(new Event('change', { bubbles: true })); + return; + } const isOpen = navLinks.classList.toggle('open'); - navToggle.classList.toggle('open'); + navToggle.classList.toggle('open', isOpen); navToggle.setAttribute('aria-expanded', String(isOpen)); + document.body.classList.toggle('nav-open', isOpen); + }); + + navLinks.querySelectorAll('a, label').forEach((item) => item.addEventListener('click', closeNavigation)); + document.addEventListener('keydown', (event) => { + if (event.key === 'Escape') closeNavigation(); }); - navLinks.querySelectorAll('a').forEach((link) => { - link.addEventListener('click', () => { - navLinks.classList.remove('open'); - navToggle.classList.remove('open'); - navToggle.setAttribute('aria-expanded', 'false'); - }); + window.addEventListener('resize', () => { + if (window.innerWidth > 1219) closeNavigation(); }); } @@ -38,8 +53,8 @@ document.addEventListener('DOMContentLoaded', () => { updateNav(); } - // ── Release banner: pill-nav offset + dismiss ─ - // The floating pill nav (landing/about) is position:fixed and would overlap + // ── Release banner: site-nav offset + dismiss ─ + // The fixed site nav (landing/about) would overlap // the in-flow banner. rfUpdateBannerOffset() publishes the still-visible // banner height as --rf-banner-h so the nav sits just below it and rises back // as it scrolls away. Dismissal is keyed to the release tag (set on the @@ -91,28 +106,13 @@ function rfUpdateBannerOffset() { return String(n); } - function animateCount(el, target, duration) { - if (typeof target !== 'number' || isNaN(target)) return; - const reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; - if (reduce) { el.textContent = fmt(target); return; } - const start = performance.now(); - function tick(now) { - const t = Math.min(1, (now - start) / duration); - const eased = 1 - Math.pow(1 - t, 3); /* easeOutCubic */ - el.textContent = fmt(Math.round(target * eased)); - if (t < 1) requestAnimationFrame(tick); - else el.textContent = fmt(target); - } - requestAnimationFrame(tick); - } - function paint(stars, forks) { targets.forEach(el => { const which = el.getAttribute('data-gh-stat'); const target = which === 'stars' ? stars : which === 'forks' ? forks : NaN; - animateCount(el, target, 900); + if (typeof target === 'number' && !isNaN(target)) el.textContent = fmt(target); }); } diff --git a/examples/rfl/market_demo.rfl b/examples/rfl/market_demo.rfl new file mode 100644 index 000000000..d892a52e7 --- /dev/null +++ b/examples/rfl/market_demo.rfl @@ -0,0 +1,21 @@ +; Website demo: aggregate 100,000 deterministic market rows. +; Run with: ./rayforce -t 1 examples/rfl/market_demo.rfl + +(set n 100000) +(set trades + (table ['symbol 'side 'price 'qty] + (list + (take ['AAPL 'MSFT 'NVDA 'GOOG] n) + (take ['BUY 'SELL] n) + (+ 100 (% (til n) 700)) + (+ 1 (% (* (til n) 17) 250))))) + +(set leaders + (select {from: trades + where: (> qty 100) + by: symbol + trades: (count qty) + notional: (sum (* price qty))})) + +(show leaders) +(exit 0) diff --git a/include/rayforce.h b/include/rayforce.h index 6a722d54d..b7d79c03c 100644 --- a/include/rayforce.h +++ b/include/rayforce.h @@ -433,13 +433,25 @@ int64_t ray_timestamp_now_ns(void); ray_t* ray_guid(const uint8_t* bytes); ray_t* ray_typed_null(int8_t type); -/* ===== Null Sentinel Values ===== +/* ===== Nullability and Null Sentinel Values ===== * - * Per-type null encoding for nullable scalar types. Callers compare values - * directly (e.g. `x == NULL_I64`, `x != x` for NaN); there are no predicate - * macros or aliases. Temporal types (DATE/TIME/TIMESTAMP) reuse NULL_I32 or - * NULL_I64 based on their storage width. SYM null = sym ID 0; STR null = - * empty string (length 0); BOOL and U8 are non-nullable. */ + * Public nullability matrix: + * + * type family atom encoding vector encoding + * ---------------- ------------------------ ------------------------ + * I16/I32/I64 width-correct NULL_* width-correct NULL_* + * DATE/TIME/TS width-correct NULL_* width-correct NULL_* + * F32/F64 NaN NaN + * GUID 16 all-zero bytes 16 all-zero bytes + * BOOL/U8 ray_typed_null aux bit non-nullable + * SYM/STR no distinct null non-nullable + * + * Empty SYM/STR values are ordinary values. A requested typed or input null + * for either type collapses to the ordinary empty value; its public null + * predicate remains false and a vector does not acquire HAS_NULLS. Callers + * compare sentinel-backed payloads directly (for example `x == NULL_I64` or + * `x != x` for NaN). Temporal types reuse NULL_I32 or NULL_I64 according to + * storage width. */ #define NULL_I16 ((int16_t)INT16_MIN) #define NULL_I32 ((int32_t)INT32_MIN) #define NULL_I64 ((int64_t)INT64_MIN) @@ -447,9 +459,9 @@ ray_t* ray_typed_null(int8_t type); #define NULL_F64 (__builtin_nan("")) /* Atom null check. RAY_NULL_OBJ is the untyped null singleton. - * Typed atoms with a defined NULL_* sentinel use payload-compare; - * types without a sentinel (BOOL/U8/F32) fall back to the - * aux[0]&1 bit written by ray_typed_null. */ + * Sentinel-backed atoms use payload comparison. BOOL/U8 typed-null atoms + * use the aux[0]&1 bit written by ray_typed_null. SYM/STR always return + * false because their empty payloads are ordinary values. */ static inline bool ray_atom_is_null_fn(const union ray_t* x) { if (RAY_IS_NULL(x)) return true; if (x->type >= 0) return false; @@ -664,10 +676,10 @@ ray_runtime_t* ray_runtime_create_with_sym_err(const char* sym_path, void ray_runtime_destroy(ray_runtime_t* rt); /* Parse and evaluate a Rayfall source string against the global env. - * Returns NULL for void / null results, an error ray_t* on failure - * (test with RAY_IS_ERR and inspect with ray_err_code), or the result - * value otherwise. Caller owns the returned reference; release with - * ray_release. */ + * Returns RAY_NULL_OBJ for successful void / null results, an error ray_t* + * on failure (test with RAY_IS_ERR and inspect with ray_err_code), or the + * result value otherwise. Caller owns the returned reference; release + * with ray_release. */ ray_t* ray_eval_str(const char* source); ray_t* ray_select(ray_t** args, int64_t n); diff --git a/mkdocs.yml b/mkdocs.yml index 5dc11ad20..a3d9835ca 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -74,8 +74,7 @@ markdown_extensions: - footnotes - md_in_html - tables - - toc: - permalink: true + - toc - pymdownx.details - pymdownx.superfences - pymdownx.tabbed: diff --git a/overrides/about.html b/overrides/about.html index dedf51770..cf8aadc07 100644 --- a/overrides/about.html +++ b/overrides/about.html @@ -75,22 +75,13 @@ {# Reuse the marketing footer from home.html so About matches the landing. #} {% block footer %} {% endblock %} diff --git a/overrides/home.html b/overrides/home.html index 7477b5339..661e67620 100644 --- a/overrides/home.html +++ b/overrides/home.html @@ -1,372 +1,309 @@ -{#- - Rayforce home — custom landing template. - - Ports the marketing landing (formerly website/index.html) into the - mkdocs build so the site can be served from a single source. This - template extends Material's main.html and overrides the layout blocks - the docs use (tabs, sidebar, content, footer) with the marketing - hero / why / features / architecture / demo / CTA sections. - - The pill nav at the top stays — it's injected via the regular header - override (overrides/partials/header.html), so landing and docs share - one nav. Landing-only styles and JS load via the styles/scripts - blocks below; everything else stays on the docs bundle. --#} {% extends "main.html" %} -{# Flag the document early (before renders) so landing-only CSS - can scope itself without FOUC. The class is set on so it's - available before body exists. CSS uses `html.landing-page` below. #} {% block extrahead %} - + {% endblock %} -{# Hide the Material tab bar on the landing page — the pill nav is enough. #} -{% block tabs %} - {{ super() }} - -{% endblock %} +{% block tabs %}{% endblock %} -{# Replace the entire docs container (sidebar + TOC + content) with the - marketing landing sections. Wrapped in body.landing-page class via JS - below so we can scope landing-only CSS without touching base.html. #} {% block container %} -
- - -
-
-
-
-
-
-
-
-
PURE C · ZERO DEPS · GRAPH + ANALYTICS
-

Columnar analytics and
graph traversal in one pipeline

-

Rayforce is a zero-dependency embeddable engine where columnar analytics and graph operations share a single operation DAG, pass through a multi-pass optimizer, and execute as fused morsel-driven bytecode. Pure C. No malloc.

-
- {# Primary CTA. Defaults to the releases page (works with no JS); - landing.js detects the OS and rewrites href + label to the - matching binary asset of the latest release. #} - - - Download - - - Docs - - - GitHub -
- +
+
+ + +
+
+

Open source · Pure C · Zero dependencies

+

Analytics and graphs.
One fast engine.

+

Rayforce fuses columnar analytics, graph traversal, and recursive queries into one embeddable execution pipeline—built for teams that measure latency in microseconds, not meetings.

+ - - + +
- {# Three display-sized stats — replaces the older 4-tile icon - grid. Reads as a single confident sentence rather than a - feature dashboard. Pass count matches the why-card list - below (type inference, constant folding, SIP, factorize, - predicate pushdown, filter reorder, fusion, DCE) — keep - these in sync if the optimizer chain changes. #} -
-
- 16K - Lines of C +
+
+ + market_demo.rfl + real engine run
-
- 0 - External Dependencies +
+
; 100,000 deterministic market rows
+(select {from: trades
+         where: (> qty 100)
+         by: symbol
+         trades: (count qty)
+         notional: (sum (* price qty))})
-
- 8 - Optimizer Passes -
-
-
-
- - -
-
-
- -

Analytics and graphs shouldn't need separate tools

-
- -
-
-
THE RAYFORCE WAY
-
    -
  • Single pipeline for analytics + graph
  • -
  • One optimizer understands both
  • -
  • Fused execution — no serialization overhead
  • -
  • Zero dependencies — embed in any C project
  • -
-
- -
-
TODAY'S APPROACH
-
    -
  • Separate tools for analytics and graphs
  • -
  • Two query languages, two data models
  • -
  • Serialize between tools on every step
  • -
  • Heavy Python dependencies for simple ops
  • -
+
+
+ Loading 100,000 rows + +
+
+
    +
  1. Load100K rows
  2. +
  3. Optimizerewrite DAG
  4. +
  5. Filter60K match
  6. +
  7. Group4 symbols
  8. +
- -
-
MULTI-PASS OPTIMIZER
-

Type inference, constant folding, sideways information passing, factorize, predicate pushdown, filter reorder, fusion, and DCE — all in one pass over the DAG before a single element is touched.

+
+
symboltradesnotional
+
NVDA15,0001,181,242,500
+
GOOG15,0001,191,143,000
+
AAPL15,0001,176,404,200
+
MSFT15,0001,185,774,600
- -
-
MORSEL EXECUTION
-

1024-element morsel-driven bytecode. Element-wise ops fuse into single-pass pipelines. Thread-local arenas keep allocation off the critical path.

+
+ Recorded with ./rayforce -t 1 + Run Rayforce live ↗
+
+
-
-
CSR GRAPH ENGINE
-

Double-indexed compressed sparse rows. Forward + reverse indices, mmap-friendly persistence, and algorithms from BFS to worst-case optimal joins.

-
+
- -
-
- -

Six pillars, one engine

-

Everything you need for columnar analytics, graph traversal, and an interactive query language in a single embeddable library.

- -
-
-
- -

Fused Execution

-
-

Morsel-driven bytecode over 1024-element chunks. Element-wise ops fused into single-pass pipelines that stay L1-resident.

-
+
+
+
+

One execution model

+

Stop moving data
between engines.

+

Relational operators and graph traversals belong in the same plan. Rayforce sees the whole workload, rewrites it together, and keeps the hot path close to the metal.

+ Explore the execution pipeline +
-
-
- -

Graph Engine

-
-

Double-indexed CSR storage. BFS, Dijkstra, A*, PageRank, Louvain, LFTJ — all in the same DAG.

-
+
+ +
+ 01 +

Compose

Tables + graphs + rules

+ Lazy DAG +
+
+ 02 +

Optimize

Rewrite the whole plan

+ Multi-pass +
+
+ 03 +

Fuse

Remove intermediates

+ Bytecode +
+
+ 04 +

Execute

Stream cache-sized morsels

+ Parallel +
+
+
-
-
- -

Rayfall Language

-
-

Lisp-like query REPL with rich builtins. Lambdas compile lazily to bytecode. select/update bridge to the DAG optimizer.

-
+
+
16Klines of focused C
+
0external dependencies
+
IPCclient/server transport built in
+
MITlicensed and embeddable
+
+
-
-
- -

Multi-Pass Optimizer

-
-

Type inference → constant folding → SIP → factorize → predicate pushdown → filter reorder → fusion → DCE.

-
+
+
+
+

Rayfall language

+

Ask complex questions.
Keep the syntax small.

+
+

Use Rayfall interactively, embed the C API, or connect through a growing set of client interfaces. Every surface reaches the same optimizer and execution core.

+
-
-
- -

Custom Allocator

-
-

Buddy allocator with slab cache. Thread-local arenas, lock-free allocation, COW ref counting. No malloc.

+
+
+ + + + One engine, three surfaces +
+
+
+
; Aggregate high-value flow, then traverse counterparties
+(set flow
+  (select {from: trades
+           where: (> Notional 1000000)
+           by: Counterparty
+           Volume: (sum Notional)}))
+
+(.graph.var-expand network flow 1 3)
+ +
- -
-
- -

Zero Dependencies

+
+
EXAMPLE RESULT5 rows · optimized
+
+
CounterpartyDepthVolume
+
LYNX_02184.20M
+
ALPHA_17261.08M
+
NODE_08244.71M
+
FORT_04327.19M
+
EDGE_12312.42M
-

Pure C, single public header. Builds with make. ~16K lines of engine code. Embeds into any C/C++ project.

- -
-
- -

Build, optimize, execute

-

Data flows from lazy DAG construction through a multi-pass optimizer into fused morsel-driven bytecode execution.

- -
- - - - - - - - - CSV / Tables - - CSR Graphs - - Rayfall - SOURCES - - - - - - - - RAYFORCE ENGINE - - scan - - filter - - group - - join - - optimizer - - fusion - - bytecode - EXECUTION - - morsel 1024 - - graph ops - - LFTJ - - - - - - - - DataFrames - - Paths - - Aggregates - RESULTS - +
+
+
+

Built as one system

+

Small footprint.
Serious machinery.

+

Everything you need to move from raw columns to connected answers—without assembling a second platform around it.

-

Lazy DAG nodes → multi-pass optimizer (type inference, SIP, factorize, predicate pushdown, fusion, DCE) → fused morsel-driven executor with register-slot bytecode. Graph algorithms run in the same pipeline — no serialization between analytics and traversal.

+
- -
-
- -

From zero to query in seconds

-

Start the REPL, build tables, aggregate, and pivot — all in one session.

+
+
+
+

In production now

+

Built for real work,
not synthetic demos.

+

Rayforce is already part of production systems at teams working with trading, market connectivity, investment analytics, and risk.

+

Production usage confirmed by the Rayforce project. Customer names link to their public company sites.

+
-
- -
-
-
-

Start building in
30 seconds

-
-
- - - -
-
$ make && ./rayforce
- (pivot t 'Symbol 'Side 'Qty sum)
+
+ + +
+
+

Rayforce Cloud Coming soon

+

The engine you can embed.
The platform you won’t have to operate.

+

We’re bringing Rayforce’s unified analytics and graph pipeline to a managed cloud experience—so teams can move from local prototype to production workload without rebuilding the data path.

+
+ Follow development + No launch date announced yet.
- + +
+
RAYFORCE / CLOUDPREVIEW
+
+

Deployment

eu-westReady
+ +
QUERY SERVICEPreview
ENGINEManaged
-
- - - +
+
+

Open at the core

+

Read every line.
Own every workload.

+

One codebase. One public header. No external runtime. Clone Rayforce, compile it, embed it, and keep control of your data path.

+ +
+
GitHub stars
+
forks
+
Linux · macOSsupported today
+
+
+
+
{% endblock %} -{# Replace Material's footer with the marketing footer so the landing - matches the design from website/index.html exactly. #} {% block footer %} {% endblock %} -{# Load landing-only JS (terminal demo, scroll fade-ins) after the - regular docs bundle so the GitHub widget counter still hooks up. #} {% block scripts %} {{ super() }} diff --git a/overrides/main.html b/overrides/main.html index c81061a7e..00caf3834 100644 --- a/overrides/main.html +++ b/overrides/main.html @@ -1,5 +1,15 @@ {% extends "base.html" %} +{% block site_meta %} + {{ super() }} + + + + + + +{% endblock %} + {# Rayforce docs — Material override entry point. @@ -13,20 +23,86 @@ announce slot (its .md-banner wrapper is flattened to display:contents in extra.css so only .rf-banner is styled). Static text works with no JS; main.js fills the live version ("Rayforce vX.Y.Z is out"), rewrites the Download link to - the OS-matched binary, offsets the floating pill nav below it, and handles + the OS-matched binary, offsets the fixed site nav below it, and handles dismissal — keyed to the release tag, so a NEW release re-shows the banner. #} {% block announce %}
- - - Rayforce — latest release +
+ Release + Rayforce — latest release Download - - + +
+ + {% endblock %} diff --git a/overrides/partials/header.html b/overrides/partials/header.html index 316d4a32d..6dc072617 100644 --- a/overrides/partials/header.html +++ b/overrides/partials/header.html @@ -1,98 +1,73 @@ -{#- - Rayforce header — conditional. - - • Landing page (page.url == ''): renders the floating-pill marketing - nav (Features / Architecture / Docs / Live Demo / About + GitHub - widget) so the home page matches the marketing brand. - - • Docs pages: renders Material's standard `.md-header` (logo + page - title + palette toggle + search + repo button) so docs pages stay - focused on docs UX — drawer toggle on mobile, search at the top - right, no marketing chrome. - - The pill-nav HTML is a verbatim port of website/index.html's