Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,40 @@ auto-generated per-PR notes; this file is the curated, human-readable history.
## [Unreleased]

### Added
- **#592: lock in the #586/#587 shell primitive guardrails mechanically.**
`check:arch` now rejects three regrowth shapes the six-copy-pasted-overlays
problem #586 fixed: (1) a new `Document.body.append`/`.appendChild` call
outside an exact, reviewed baseline snapshot of sanctioned lifecycle/
primitive scopes (`shell-body-mount`); (2) a new `position: fixed` CSS
declaration in `src/styles.css` outside the current selector/at-rule
snapshot (`shell-fixed-position`, a focused CSS lexical scanner — no CSS
parser dependency); (3) a new global capture-phase Escape `keydown`
lifecycle outside `SurfaceLifecycle` and its exact documented exceptions/
non-panel gesture exclusions (`shell-capture-escape`). The two source-level
rules share one real-TypeScript-parser batch
(`findShellGuardrailSourceContractViolations`,
`build/lib/check-legacy-owners.mjs`), reusing this repo's established
`withParsedSources`/`walkTree`/`SyntaxKind` idiom — no new parser
dependency. Root-cause circuit breaker (post-merge): three code-review
passes each found a variant of one root cause — a hand-rolled, Map-based
scope/alias-resolution layer re-deriving JS/TS name-binding semantics by
hand. Restructured to resolve every identifier-binding question (a
Document/Window alias, an `addEventListener` handler/capture-options
alias) through the REAL TypeScript checker `withParsedSources` already
had available (`checker.getSymbolAtLocation` against the identifier's
resolved declaration) instead — same-function block shadowing, for-loop-
header shadowing, and correct reversion after a shadow's scope ends are
now real binder behavior, not custom scope-tracking code; the retired
helpers (`scopeOwnerOf`/`scopeChain`/`buildGlobalAliasMap`/
`buildFunctionDeclMap`/`buildCaptureAliasMap`) are gone. Candidate
discovery (the AST shapes for `.appendChild`/`.append`/`addEventListener`/
Escape comparisons) and the CSS `shell-fixed-position` scanner are
unchanged; the three rules' policy and diagnostic shapes are unchanged.
Also closes the inherited #586/#593-phase-1 finding: an
independent `tests/unit/resize-handle-thickness-contract.test.js` proves
`src/ui/app-shell.ts`'s `HANDLE_PX` and `src/styles.css`'s
`.col-resize`/`.inspector-resize` width cannot drift unnoticed. Enforcement-
only — no runtime UI/DOM/CSS behavior changes.
- **#630 Phase 8 (final phase — closes #630): make `@altinity/clickhouse-http`
independently buildable/packable/typecheckable in isolation, and retire the
`@clickhouse/client-web` vendor-comparison spike.** Claims A17/A18.
Expand Down
60 changes: 60 additions & 0 deletions build/check-boundaries.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ import {
retiredClientSpikeScriptNames,
findDynamicImportUsages,
mightContainDynamicImport,
findShellGuardrailSourceContractViolations,
findShellFixedPositionViolations,
findShellFixedPositionMissingBaselineViolations,
} from './lib/check-legacy-owners.mjs';

const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
Expand Down Expand Up @@ -886,6 +889,63 @@ for (const file of collectFiles(path.join(repoRoot, 'src'))) {
}
}

// Issue #592 — shell primitive guardrails: lock in what #586 ("one overlay-
// lifecycle implementation") and #587 ("a registry-driven panel model")
// established, so the six-copy-pasted-overlays problem #586 fixed cannot
// silently regrow. Two rules share ONE real-TypeScript-parser batch over the
// whole scanned `src/**` tree (`findShellGuardrailSourceContractViolations`,
// `build/lib/check-legacy-owners.mjs` — Architecture decision 4: never one
// parser process per rule or per file — including its own complete-tree
// reverse-baseline half, folded in below via `completeTree: true` rather
// than a second call opening a second batch, #592 review pass 2, ChatGPT PR
// #672 pass 2 P2); a third is a focused CSS lexical scanner over
// `src/styles.css` alone (`findShellFixedPositionViolations` — Architecture
// decision 2: no CSS parser dependency). `lineOfOffset` converts
// each analyzer's raw AST/lexer byte offset into the 1-based line number this
// gate's own diagnostics use everywhere else.
function lineOfOffset(source, pos) {
return source.slice(0, pos).split('\n').length;
}
{
const shellGuardedFiles = collectFiles(path.join(repoRoot, 'src'));
const shellSources = shellGuardedFiles.map((file) => {
const relFile = path.relative(repoRoot, file).split(path.sep).join('/');
checkedFiles += 1;
return { filename: relFile, source: guardedFileSources.get(file) ?? fs.readFileSync(file, 'utf8') };
});
const bySource = new Map(shellSources.map((s) => [s.filename, s.source]));
// `completeTree: true` folds the complete-tree reverse-baseline half (PR
// #672 review pass 1 follow-up, ChatGPT) into this SAME shared parser
// batch — meaningful only against the real, complete `src/**` tree, which
// `shellSources` (built from `collectFiles`'s live disk walk) always is
// here. #592 review pass 2 (ChatGPT PR #672 pass 2 P2): this used to be a
// separate call to `findShellGuardrailMissingBaselineViolations` over the
// identical `shellSources`, which opened its OWN second parser batch —
// see `findShellGuardrailSourceContractViolations`'s own doc comment for
// why that violated this file's "ONE shared parser batch" contract.
for (const v of findShellGuardrailSourceContractViolations(shellSources, { completeTree: true })) {
const line = lineOfOffset(bySource.get(v.filename) ?? '', v.pos);
violations.push(`${v.filename}:${line} → ${v.rule}: ${v.detail}`);
}

const stylesPath = path.join(repoRoot, 'src/styles.css');
if (fs.existsSync(stylesPath)) {
checkedFiles += 1;
const cssSource = fs.readFileSync(stylesPath, 'utf8');
for (const v of findShellFixedPositionViolations(cssSource, 'src/styles.css')) {
const line = lineOfOffset(cssSource, v.pos);
violations.push(`${v.filename}:${line} → ${v.rule}: ${v.detail}`);
}
// The reverse half of the #672 P1 fix — an approved fixed-position
// fingerprint that disappeared from the CSS entirely. Meaningful only
// against the real, complete stylesheet (see the function's own doc
// comment), which this gate always reads from disk.
for (const v of findShellFixedPositionMissingBaselineViolations(cssSource, 'src/styles.css')) {
violations.push(`${v.filename}:${lineOfOffset(cssSource, v.pos)} → ${v.rule}: ${v.detail}`);
}
}
}

if (violations.length) {
console.error('check-boundaries: architecture violations:');
for (const line of violations) console.error(` ${line}`);
Expand Down
132 changes: 131 additions & 1 deletion build/lib/check-legacy-owners.d.mts
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,22 @@ export type SurfaceLifecycleRule =
| 'surface-current-workspace-null'
| 'surface-retirement-ordering';

/** The #592 shell-primitive-guardrail rule codes
* `findShellGuardrailSourceContractViolations` (`shell-body-mount` /
* `shell-capture-escape`) and `findShellFixedPositionViolations`
* (`shell-fixed-position`) may report. */
export type ShellGuardrailRule =
| 'shell-body-mount'
| 'shell-capture-escape'
| 'shell-fixed-position';

/** One reported source-contract violation — a plain, JSON-serializable DTO.
* `pos` is the offending AST node's own `getStart(sourceFile)` (or `0` for a
* whole-file "the required construct is entirely absent" finding, which
* names no single node): a stable, deterministic identity, never a
* line/column and never required in a user-facing diagnostic. */
export interface SourceContractViolation {
readonly rule: SidePanelRule | SurfaceLifecycleRule;
readonly rule: SidePanelRule | SurfaceLifecycleRule | ShellGuardrailRule;
readonly filename: string;
readonly pos: number;
readonly detail: string;
Expand Down Expand Up @@ -95,3 +104,124 @@ export function findSurfaceLifecycleSourceContractViolations(
sources: readonly SurfaceLifecycleSourceEntry[],
options: SurfaceLifecycleOptions,
): SourceContractViolation[];

/** One (filename, raw source) entry in a #592 shell-guardrail batch —
* structurally identical to `SurfaceLifecycleSourceEntry` (both are just
* "a repo-relative filename plus that file's complete, unmodified text"),
* named separately so `findShellGuardrailSourceContractViolations`'s own
* signature documents its own #592 contract rather than borrowing a #590-
* named type. */
export interface ShellGuardrailSourceEntry {
readonly filename: string;
readonly source: string;
}

/** Options for `findShellGuardrailSourceContractViolations`. */
export interface ShellGuardrailSourceContractOptions {
/** Fold `findShellGuardrailMissingBaselineViolations`'s own complete-tree
* reverse-baseline check into this call's SAME shared parser batch,
* instead of a caller opening a second batch to get it separately (#592
* review pass 2, ChatGPT PR #672 pass 2 P2). Only pass `true` when
* `sources` really is the complete scanned tree — see
* `findShellGuardrailMissingBaselineViolations`'s own doc comment for why. */
readonly completeTree?: boolean;
}

/**
* The #592 shell-primitive-guardrail source contract (`shell-body-mount` +
* `shell-capture-escape`), real-TypeScript-parser-backed, over ONE shared
* parser batch for the complete `sources` set (never one parser process per
* rule or per file). `options.completeTree: true` additionally folds in the
* complete-tree reverse-baseline violations
* `findShellGuardrailMissingBaselineViolations` would otherwise report
* separately, over this SAME batch.
*/
export function findShellGuardrailSourceContractViolations(
sources: readonly ShellGuardrailSourceEntry[],
options?: ShellGuardrailSourceContractOptions,
): SourceContractViolation[];

/**
* The complete-tree REVERSE half of the #592 shell-guardrail source
* contract (`shell-body-mount` + `shell-capture-escape`) — deliberately
* SEPARATE from `findShellGuardrailSourceContractViolations`, for the same
* reason `findShellFixedPositionMissingBaselineViolations` is separate from
* `findShellFixedPositionViolations` (see that pair's own doc comments): the
* forward check's own softened reverse pass (`declaredScopeKeys`) exists
* ONLY to keep it safe for this suite's many minimal single-scope synthetic
* fixtures, and cannot distinguish a genuinely complete file with an
* approved function/scope deleted from a fixture that never declared that
* scope to begin with. This export assumes `sources` IS the complete
* scanned tree (its only real callers are `build/check-boundaries.mjs`'s
* live `collectFiles(src/)` batch — directly, and via
* `findShellGuardrailSourceContractViolations`'s own `completeTree: true`
* mode, #592 review pass 2) and reports, without that softening, every
* `SHELL_BODY_MOUNT_POLICY`/`SHELL_CAPTURE_ESCAPE_POLICY` entry whose
* approved occurrence count is not met in `sources` — covering a whole
* approved FILE missing from `sources` entirely, a whole approved
* function/scope deleted (or renamed) from a still-present file, and a
* dropped occurrence count within a still-present scope, uniformly (PR #672
* review pass 1 follow-up, ChatGPT). This standalone export still opens its
* own parser batch when called directly (kept for every existing caller
* with no batch already open); the production `check:arch` wiring instead
* reaches this identical logic through `findShellGuardrailSourceContractViolations`'s
* `completeTree` mode, so the production path never opens two.
*/
export function findShellGuardrailMissingBaselineViolations(
sources: readonly ShellGuardrailSourceEntry[],
): SourceContractViolation[];

/** One `position: fixed` (optionally `!important`) CSS declaration found by
* `scanFixedPositionDeclarations` — `selector` is the enclosing rule's own
* normalized (whitespace-collapsed, comma-list-normalized) prelude; `atRule`
* is the FULL chain of enclosing AT-RULE ancestors' normalized preludes,
* outermost first, joined with `' > '` (e.g. `'@media (max-width: 768px)'`,
* or `'@supports (display: grid) > @media (max-width: 768px)'` for a rule
* nested under both), or `null` when no at-rule ancestor exists; `nested` is
* `true` when the declaration's own rule sits inside another PLAIN style
* rule (real CSS nesting — a genuinely different, never-approved effective
* selector, e.g. a descendant selector — ChatGPT PR #672 pass 1); `pos` is
* the declaration's own offset into the scanned CSS text (the first
* non-whitespace, non-comment character). */
export interface FixedPositionDeclaration {
readonly selector: string;
readonly atRule: string | null;
readonly nested: boolean;
readonly pos: number;
}

/**
* The focused CSS lexical scanner (Architecture decision 2, #592) — no CSS
* parser dependency. Skips CSS block comments, respects quoted strings and
* escapes, tracks brace nesting, and normalizes whitespace/comma-selector-
* lists deterministically; see the `.mjs` implementation's own doc comment
* for the full contract.
*/
export function scanFixedPositionDeclarations(source: string): FixedPositionDeclaration[];

/**
* The `shell-fixed-position` guard's FORWARD half: every
* `scanFixedPositionDeclarations` result in `cssSource` beyond its exact
* `(selector, atRule)` fingerprint's approved COUNT (never a mere membership
* check — a duplicate of an approved fingerprint is flagged too, PR #672
* review pass 1). A `nested` declaration (real CSS nesting under another
* plain style rule) is unconditionally flagged instead — never compared by
* fingerprint at all (PR #672 review pass 1 follow-up, ChatGPT).
*/
export function findShellFixedPositionViolations(
cssSource: string,
filename: string,
): SourceContractViolation[];

/**
* The `shell-fixed-position` guard's REVERSE half (PR #672 review pass 1):
* every frozen `SHELL_FIXED_POSITION_POLICY` fingerprint with ZERO matching
* occurrences in `cssSource` — meaningful only against the real, complete
* `src/styles.css` (see the `.mjs` implementation's own doc comment for why
* this is a separate export from `findShellFixedPositionViolations` rather
* than folded into it).
*/
export function findShellFixedPositionMissingBaselineViolations(
cssSource: string,
filename: string,
): SourceContractViolation[];
Loading