Skip to content

Polish batch: owner entityId stamping, unused timezone, lenient date validation, silent null content filter #69

Description

@cuibonobo

Small correctness/consistency fixes from the design-review backlog that don't warrant individual issues. Items that belong to existing issues went there instead: FK pragma + token-storage follow-through → #46 (comment), malformed-cursor 500s → #53 (comment), revoke()/listGrants()#57 (comment).

1. Owner via ScopedStack gets entityId stamped, contradicting the spec

Spec §Records: "Absent means owner-created — Records written directly by the stack owner carry no entityId." But ScopedStack.create() stamps unconditionally — "The created record's entityId is always set to the requester" (packages/core/src/stack.ts:880, 892) — so the same owner writing through asEntity(ownerEntityId) (i.e., through any server) produces records that do carry it. Same author, two representations; every "is this owner-authored" check must now know both. Fix: ScopedStack.create() omits entityId when the requester is the owner. Decide + test the same normalization for -own grant checks (hasGrant compares record.entityId === requester, which never matches owner records once normalized — correct, since the owner bypasses grants, but worth a test pinning it). Interacts with #49 (DID identity) and #57 (authorship semantics).

2. timezone: required at init, consumed by nothing

Required at initialize(), stored in _config, surfaced in discovery — and no code path reads it for any behavior. It's a presentation concern living at the data layer. Fix: make it optional at init (default absent, not 'UTC' — a default pretends knowledge), keep it as passthrough app metadata in _config/discovery, and say so in the spec. Removing it entirely is needless churn; requiring it is a lie.

3. date fields validated with bare Date.parse

packages/core/src/validate.ts:78 — the error message promises "Expected ISO 8601 date string" but Date.parse accepts "March 1 2020", "3/1/2020", and other engine-dependent formats, so cross-runtime stacks can disagree about validity and stored values aren't canonical. Fix: validate against an ISO 8601 shape (regex + Date.parse sanity check), matching the message the code already emits. Tightening is a behavior change on paper; per the no-install-base policy (#57) it's free now and only gets more expensive.

4. content: { x: null } filter silently matches nothing

A content filter with a null value matches no records on the SQL adapters (SQL = NULL semantics) — but the natural reading is "records where x is absent/null," and the caller gets an empty result with no signal, the same silent-superset/subset shape as #56. Fix: pick and spec a meaning — leaning null = "field absent or null" (IS NULL / JSON-path-missing semantics) since "match nothing" is never what anyone means; if instead it's rejected, reject loudly (StackValidationError), never silently. Conformance fixture either way (#52).

Work items

Refs #49, #50, #52, #56, #57.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions