@open-predicate/open-predicate 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,46 @@ minor release may break compatibility, in which case the break is spelled out be
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.6.1] — 2026-09-14
11
+
12
+ **A packaging release.** No change to the grammar, the schema or the semantics of evaluation:
13
+ `open-predicate-schema.json` is byte-identical to 0.6.0, and the `$id` still names `v0.4.0`. What
14
+ changed is that the package now works the way 0.6.0 said it did.
15
+
16
+ ### Fixed
17
+
18
+ - **The CLI did nothing when invoked as a CLI.** `tools/generate-filter-schema.mjs` guarded its entry
19
+ point with `fileURLToPath(import.meta.url) === process.argv[1]`, and npm installs a `bin` as a
20
+ **symlink** — so `argv[1]` was `node_modules/.bin/open-predicate-generate` while `import.meta.url`
21
+ was the file it pointed at. The comparison was false, `main()` never ran, and the process exited
22
+ **0 having printed nothing**. Every `npx @open-predicate/open-predicate` and every global install
23
+ was affected in `0.6.0`, the first release to ship a `bin` at all.
24
+
25
+ It went unnoticed because the two ways it is exercised here both avoid the symlink:
26
+ `npm run generate:example` and the tests call the file by path. The same comparison also failed for
27
+ a plain path invocation anywhere under a symlinked directory — including `/tmp` on macOS, which is
28
+ a symlink to `/private/tmp`.
29
+
30
+ The guard now compares through `realpathSync` on both sides, and `tests/generator.test.mjs` runs
31
+ the real generator through a real symlink and requires output, so the regression cannot return. It
32
+ still must not run on import, and that is asserted in the same test.
33
+
34
+ - **`engines` was missing, so an unsupported Node failed obscurely.** The package now declares
35
+ `"node": ">=20.10.0"`. That is the real floor, not a guess: the generator uses `parseArgs` from
36
+ `node:util`, and the consumption path the README documents —
37
+ `import schema from '@open-predicate/open-predicate' with { type: 'json' }` — needs import
38
+ attributes, which is 20.10. Without the field, npm had nothing to warn against and a user on an
39
+ older line got a stack trace instead of a version complaint.
40
+
41
+ - **The release pipeline never exercised the artifact it publishes.** `npm test` and
42
+ `npm run generate:example` both invoke the generator by path, which is the one way that never
43
+ crosses the symlink npm installs a `bin` as — so the no-op above passed every check and shipped.
44
+ [`.github/workflows/release.yml`](./.github/workflows/release.yml) now packs the tarball, installs
45
+ it into a scratch project the way a consumer would, and drives every entry point the README
46
+ documents: the bin through its symlink, `require()`, the ESM import attribute, and
47
+ `./generate`. It also asserts the generator stays silent when merely imported. A release cannot
48
+ now ship an artifact whose documented entry points do not work.
49
+
10
50
  ### Changed
11
51
 
12
52
  - **The npm package is scoped: `@open-predicate/open-predicate`.** 0.6.0 named it `open-predicate`,
@@ -27,13 +67,63 @@ minor release may break compatibility, in which case the break is spelled out be
27
67
 
28
68
  ### Added
29
69
 
70
+ - **The project has a written governance and contribution process.**
71
+ [`GOVERNANCE.md`](./GOVERNANCE.md) states how a decision is made and what it costs — editorial,
72
+ substantive-compatible, or normative, where normative requires a record under
73
+ [`decisions/`](./decisions), a migration note and a `$id` bump. It sets out how a disputed design
74
+ call is resolved (answered in writing, then a decision record quoting the objection in the
75
+ objector's words, then 14-day lazy consensus, then the editor decides **and the dissent is recorded
76
+ in the record**), what earns commit rights, and an explicit royalty-free patent posture — MIT
77
+ settles copyright and says nothing about patents, which is the first thing an adopter's lawyer
78
+ looks for. It also states that there is currently one maintainer and calls that a defect rather
79
+ than a design.
80
+
81
+ [`CONTRIBUTING.md`](./CONTRIBUTING.md) leads with the thing the README already says is most useful
82
+ — *disagreement* — and makes the three entry points concrete: file a design objection, build an
83
+ implementation, or claim conformance for a library that already exists. It is candid that
84
+ `tests/fixtures/` checks schema well-formedness only, has no records or expected results, uses an
85
+ ajv-flavoured `expectKeyword`, and is excluded from the published package; and it names promoting
86
+ `experiments/filter-to-sql/cases.mjs` (73 cases over 10 records, already shaped
87
+ `{group, id, title, filter, expect}`) into a portable conformance suite as the highest-value
88
+ contribution currently available.
89
+
90
+ Also added: [`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md), which makes explicit that blunt technical
91
+ disagreement is welcome and is not incivility, and discloses that a report about the sole
92
+ maintainer has nowhere independent to go inside the project;
93
+ [`SECURITY.md`](./SECURITY.md), which defines what a vulnerability even means for a specification
94
+ — a rule that makes conforming implementations unsafe, a filter that stays inside the §7 limits and
95
+ is still superlinear, or any path where a predicate ends up dropped, widened or truncated, since
96
+ that is an authorization bypass wherever filters carry tenancy; [`SUPPORT.md`](./SUPPORT.md); three
97
+ issue forms; and a pull-request template whose checklist is tied to the invariants the tests
98
+ already enforce. Discussions and private vulnerability reporting are enabled on the repository.
99
+
100
+ - **The schema is served from its `$id`.**
101
+ `https://openpredicate.tech/schema/v0.4.0/open-predicate-schema.json` now returns the file it
102
+ identifies, as `application/schema+json`, with `Access-Control-Allow-Origin: *` so browser-based
103
+ tooling can fetch it and `Cache-Control: public,max-age=31536000,immutable` because a versioned
104
+ `$id` never changes (§9). The served bytes are identical to
105
+ [`open-predicate-schema.json`](./open-predicate-schema.json) in this repository.
106
+
107
+ This closes what the README called the one piece of remaining work. The `$ref`-by-URL workflow
108
+ that [Using it from OpenAPI](./README.md#using-it-from-openapi) and the capability document
109
+ examples are written around now describes today rather than an intended end state, and the five
110
+ RFC 9457 problem types under `https://openpredicate.tech/problems/` dereference as well. There is
111
+ deliberately **no** unversioned or `latest` schema URL; both 404.
112
+
113
+ Serving it is a second repository's job, so a release is not finished when the tag is pushed —
114
+ [`OpenPredicate/openpredicate.tech`](https://github.com/OpenPredicate/openpredicate.tech) vendors
115
+ the artefacts and has to be synced at the tag. That step is now written down in
116
+ [`RELEASING.md`](./RELEASING.md#serving-the-schema-from-its-id).
117
+
30
118
  - **Publishing is back on, and npmjs.com authenticates by OIDC.**
31
119
  [`.github/workflows/release.yml`](./.github/workflows/release.yml) publishes to both registries
32
120
  when a GitHub Release is published. The npmjs job uses npm's [trusted
33
121
  publishing](https://docs.npmjs.com/trusted-publishers): it requests `id-token: write` and npm
34
122
  exchanges that for a short-lived credential, so there is **no `NPM_TOKEN` secret** in this
35
123
  repository and nothing to rotate. Provenance is attached automatically, linking each tarball to
36
- the workflow run and commit that built it. GitHub Packages stays token-authenticated it has no
124
+ the workflow run and commit that built it though only for tarballs the workflow publishes, so
125
+ 0.6.0 has none: its bootstrap publish came from a laptop, which is the one publish OIDC cannot
126
+ do. 0.6.1 is the first over OIDC. GitHub Packages stays token-authenticated — it has no
37
127
  OIDC equivalent — but `GITHUB_TOKEN` is minted per run and expires with it.
38
128
 
39
129
  `.github/scripts/version-published.sh` is restored alongside, so each job skips a version it has
@@ -43,10 +133,16 @@ minor release may break compatibility, in which case the break is spelled out be
43
133
  breaks publishing until the trusted publisher is updated to match.
44
134
 
45
135
  **npm cannot mint a package's first version over OIDC**, because a trusted publisher can only be
46
- attached to a package that already exists. Claiming `@open-predicate/open-predicate` is therefore
47
- a one-time manual publish, documented in
48
- [`RELEASING.md`](./RELEASING.md#trusted-publishing-and-the-one-time-bootstrap). Until it has run,
49
- neither registry has a copy and the README's *Status* table says so.
136
+ attached to a package that already exists. Claiming `@open-predicate/open-predicate` was therefore
137
+ a one-time manual publish, documented along with everything it cost to get right in
138
+ [`RELEASING.md`](./RELEASING.md#trusted-publishing-and-the-one-time-bootstrap) including that
139
+ *configuring* the trusted publisher needs npm >= 12, which fails with an unexplained `E400` on
140
+ npm 11 because the older client omits the `permissions` field the registry now requires.
141
+
142
+ - **`@open-predicate/open-predicate` is on npmjs.com**, public and installable, from `0.6.0` on. The
143
+ package is the schema: `require()` it, or `import` it with `{ type: 'json' }`. The trusted
144
+ publisher is configured, so every release from here is published by the workflow rather than by
145
+ hand. GitHub Packages gets its first copy with the next release.
50
146
 
51
147
  ## [0.6.0] — 2026-09-13
52
148
 
@@ -147,10 +243,11 @@ document's top-level members. No filter valid under 0.4.0 becomes invalid.
147
243
  `--max-filter-depth` is given and `maxDepth` is not, the enforced bound is published.
148
244
  - **The generator is part of the package.** `tools/` was not in `package.json` `files` and there
149
245
  was no `bin` entry, so the tool the README points readers at could not travel with the package
150
- at all. It is now a `bin` named `open-predicate-generate`, with `open-predicate/generate` exporting
151
- `generateFilterSchema` for programmatic use. This repository still publishes no artifacts
152
- ([RELEASING.md](./RELEASING.md)), so the command is reachable from a clone or a git install and
153
- not from npmjs; what changed is that it is ready to be, and `npm pack` now contains it.
246
+ at all. It is now a `bin` named `open-predicate-generate`, with
247
+ `@open-predicate/open-predicate/generate` exporting `generateFilterSchema` for programmatic use.
248
+ At the time of this release the repository still published no artifacts, so the command was
249
+ reachable from a clone or a git install and not from npmjs; what changed here is that it was
250
+ ready to be, and `npm pack` contained it. Publishing arrived in 0.6.0.
154
251
  - **[SPEC.md §2.2](./SPEC.md#22-capability-discovery) documents the capability document's
155
252
  top-level members** — `queryLanguage`, `profiles`, `fields`, `limits` and `filterSchema` — in a
156
253
  table beside the existing per-field one. `limits` appeared in the example and in no table, and
@@ -523,6 +620,7 @@ Initial research draft: `$and`, `$or`, `$not` over eight leaf condition types
523
620
  (`$eq`, `$ne`, `$in`, `$nin`, `$like`, `$nlike`, `$gt`/`$gte`/`$lt`/`$lte`/`$between`, `$isnull`),
524
621
  laid out as an OpenAPI `components.schemas` fragment.
525
622
 
623
+ [0.6.1]: https://github.com/OpenPredicate/open-predicate/compare/v0.6.0...v0.6.1
526
624
  [0.6.0]: https://github.com/OpenPredicate/open-predicate/compare/v0.5.0...v0.6.0
527
625
  [0.5.0]: https://github.com/OpenPredicate/open-predicate/compare/v0.4.0...v0.5.0
528
626
  [0.4.0]: https://github.com/OpenPredicate/open-predicate/compare/v0.3.1...v0.4.0
package/README.md CHANGED
@@ -21,6 +21,7 @@ Write the filter grammar once. Use it for every `POST /…/search` and `QUERY /
21
21
 
22
22
  One schema, two integration points, because JSON Schema is what both already speak: it is the interchange format of OpenAPI 3.1, and it is what an MCP `inputSchema` is.
23
23
 
24
+ - **Install** — `npm i @open-predicate/open-predicate` (Node >= 20.10) ([npmjs.com](https://www.npmjs.com/package/@open-predicate/open-predicate)). The package *is* the schema: `require()` it, or `import schema from '@open-predicate/open-predicate' with { type: 'json' }` — ESM needs that import attribute, and a bare `import` throws without it.
24
25
  - **Schema** — [`open-predicate-schema.json`](./open-predicate-schema.json) (JSON Schema draft 2020-12)
25
26
  - **Semantics** — [`SPEC.md`](./SPEC.md) — nulls, paths, coercion, errors, limits
26
27
  - **MCP server** — [`examples/mcp-server/`](./examples/mcp-server) — a search tool with the language as its `inputSchema`, runnable
@@ -28,9 +29,9 @@ One schema, two integration points, because JSON Schema is what both already spe
28
29
  - **Generator** — [`tools/generate-filter-schema.mjs`](./tools/generate-filter-schema.mjs) — turns a resource's JSON Schema, plus the slice of the language you can serve, into a per-field filter schema
29
30
  - **Compared with GraphQL** — [`COMPARISON.md`](./COMPARISON.md) — what this overlaps with, what it does not, and what a JSON-Schema-native alternative would still need
30
31
  - **Stewardship** — the [OpenPredicate](https://openpredicate.tech) organisation — see [About OpenPredicate](#about-openpredicate)
31
- - **Version** — `0.5.0`. The schema's `$id` still names `v0.4.0`: the `$id` tracks the grammar, and 0.5.0 changed only the tooling and what it may claim. See [`CHANGELOG.md`](./CHANGELOG.md) for this release, and [`decisions/0001`](./decisions/0001-array-quantifiers-and-unknown-handling.md) for the v0.4.0 migration.
32
+ - **Version** — `0.6.1`. The schema's `$id` still names `v0.4.0`: the `$id` tracks the grammar, and neither 0.5.0 nor 0.6.0 touched it. See [`CHANGELOG.md`](./CHANGELOG.md) for this release, and [`decisions/0001`](./decisions/0001-array-quantifiers-and-unknown-handling.md) for the v0.4.0 migration.
32
33
 
33
- > **Work in progress — but no longer in name.** This is a design published for review, not a distribution you can depend on yet. The name is now settled: *OpenPredicate*, stewarded by the [OpenPredicate](https://openpredicate.tech) organisation, with every identifier derived from it — the repository, both package names, the schema `$id`, the problem-type URIs fixed in the one pass this README used to promise. What remains provisional is *availability*, not naming: the schema is not yet served from `openpredicate.tech` and nothing is published to a registry. The grammar and its semantics are the part worth reviewing. See [Status](#status) before you try to install or `$ref` anything.
34
+ > **Pre-1.0, and installable.** The name is settled *OpenPredicate*, stewarded by the [OpenPredicate](https://openpredicate.tech) organisation, with the repository, both package names, the schema `$id` and the problem-type URIs all derived from it — the package is on npm as [`@open-predicate/open-predicate`](https://www.npmjs.com/package/@open-predicate/open-predicate), and the schema is **served at its `$id`** so `$ref` it by URL, or from a packaged copy, whichever suits. One thing is still provisional: the grammar may still break before 1.0, with each break recorded in [`CHANGELOG.md`](./CHANGELOG.md). See [Status](#status) for what is reachable today.
34
35
 
35
36
  ---
36
37
 
@@ -234,6 +235,7 @@ Both work, and they trade off differently:
234
235
  | | Absolute `$id` URL | Bundled copy |
235
236
  | --- | --- | --- |
236
237
  | `$ref` | `https://…/v0.4.0/open-predicate-schema.json` | `./schemas/open-predicate-schema.json` |
238
+ | Where the copy comes from | resolved at use | `npm i @open-predicate/open-predicate`, or `curl` from raw GitHub |
237
239
  | Upgrades | change one URL | re-vendor the file |
238
240
  | Tooling | needs a resolver that fetches remote refs | works everywhere |
239
241
  | MCP `inputSchema` | no — nothing on that path resolves remote refs | yes, and it is the only option |
@@ -371,7 +373,7 @@ Opt a single property out, or override its operators, from the resource schema i
371
373
 
372
374
  `--include`, `--exclude`, `--max-depth` and `--pointer` do the rest. Run `--help` for the full list.
373
375
 
374
- The tool ships inside the package as a `bin` named `open-predicate-generate`, so from a registry it is `npx @open-predicate/open-predicate` rather than a path — the package name, not the bin name, because the package is scoped and `npx` resolves packages. See [Status](#status) for whether that is reachable yet; from a clone or `npm install github:OpenPredicate/open-predicate` it always is.
376
+ The tool ships inside the package as a `bin` named `open-predicate-generate`, so from a registry it is `npx @open-predicate/open-predicate` rather than a path — the package name, not the bin name, because the package is scoped and `npx` resolves packages. From a clone it stays `node tools/generate-filter-schema.mjs`.
375
377
 
376
378
  ## Exposing search to an agent
377
379
 
@@ -428,8 +430,11 @@ A filter is user input that becomes a query plan. [SPEC.md §7](./SPEC.md#7-safe
428
430
  ```
429
431
  open-predicate-schema.json the schema — the only file you need to consume
430
432
  SPEC.md normative semantics
431
- RELEASING.md how a release is cut (no artifacts are published)
433
+ CONTRIBUTING.md how to object, implement, or claim conformance
434
+ GOVERNANCE.md how decisions get made, who makes them, and the IPR posture
435
+ RELEASING.md how a release is cut, and how trusted publishing is set up
432
436
  COMPARISON.md how this relates to GraphQL, OData and JSON:API
437
+ decisions/ one numbered record per significant design decision
433
438
  tools/generate-filter-schema.mjs resource schema + capability selection -> filter schema + capabilities
434
439
  examples/mcp-server/ a runnable MCP server; the language as a tool's inputSchema
435
440
  examples/ working OpenAPI 3.1 and 3.2 documents
@@ -439,34 +444,54 @@ tests/generator.test.mjs the generator: narrowing, soundness, recursion
439
444
  tests/fixtures/valid/ one per operator; also the docs' example set
440
445
  tests/fixtures/invalid/ every defect this version fixes, pinned
441
446
  experiments/filter-to-sql/ an exercise: compile a filter to SQL, then judge the design by it
447
+ .github/ISSUE_TEMPLATE/ design objection, spec ambiguity, implementation report
442
448
  .github/workflows/ci.yml tests on Node 20/22/24 + OpenAPI lint
443
- .github/workflows/release.yml verifies a GitHub Release; publishes nothing
449
+ .github/workflows/release.yml publishes to npmjs over OIDC when a GitHub Release is published
444
450
  ```
445
451
 
446
452
  `npm test` meta-validates the schema under ajv's strict mode, checks that `x-profiles` covers exactly the operators the grammar defines, validates every inline example against its own subschema, and runs all fixtures — invalid ones asserting *which* keyword rejected them, so a fixture cannot pass for the wrong reason. It also compiles the generated pet filter schema, asserts that everything it accepts the published grammar accepts too, and checks the committed `examples/pet.filter.json` against a fresh run so it cannot drift. `npm run generate:example` refreshes it.
447
453
 
448
454
  ## Status
449
455
 
450
- **Work in progress.** Pre-1.0. This repository is published so the design can be read and argued with; it is not yet packaged for consumption, and the two should not be confused.
456
+ **Work in progress.** Pre-1.0. The design is published so it can be read and argued with, and the package is installable but the grammar may still break before 1.0, so pin an exact version if you depend on it.
451
457
 
452
458
  **The name is settled.** *OpenPredicate* is the name; [`OpenPredicate/open-predicate`](https://github.com/OpenPredicate/open-predicate) is the home; `openpredicate.tech` is the namespace every identifier derives from — the repository, both package names, the schema `$id`, the problem-type URIs, the CLI name and the vendor keyword alike. [`CHANGELOG.md`](./CHANGELOG.md) lists them in one table.
453
459
 
454
- **What is settled is the naming, not yet the availability.** The identifiers below are final, but not all of them are reachable yet — so nobody has to discover it the hard way:
460
+ **Naming and availability are both settled.** The identifiers below are final and reachable. One exception remains GitHub Packagesand it is called out so nobody has to discover it the hard way:
455
461
 
456
462
  | What the README says | Reality today |
457
463
  | --- | --- |
458
- | `$id` / `$ref` — `https://openpredicate.tech/schema/v0.4.0/open-predicate-schema.json` | The permanent namespace, but not served yet. Used throughout [Using it from OpenAPI](#using-it-from-openapi) and in the capability document examples. |
459
- | The package names `@open-predicate/open-predicate` and `@openpredicate/open-predicate` | The release pipeline publishes both on a GitHub Release — npmjs.com by OIDC trusted publishing, GitHub Packages by `GITHUB_TOKEN`. npm cannot mint the *first* version over OIDC, though, so the npmjs name is claimed by a one-time manual bootstrap; until that has run, neither registry has a copy. [`RELEASING.md`](./RELEASING.md#trusted-publishing-and-the-one-time-bootstrap) has the procedure. |
464
+ | `$id` / `$ref` — `https://openpredicate.tech/schema/v0.4.0/open-predicate-schema.json` | **Served**, as `application/schema+json`, with `Access-Control-Allow-Origin: *` and immutable caching, byte-identical to the copy in this repository. `$ref` it directly, as [Using it from OpenAPI](#using-it-from-openapi) and the capability document examples do. |
465
+ | [`@open-predicate/open-predicate`](https://www.npmjs.com/package/@open-predicate/open-predicate) on npmjs.com | **Published**, from `v0.6.0` on. Public, no credential needed to install. |
466
+ | `@openpredicate/open-predicate` on GitHub Packages | Not published yet — the first copy goes up with the next GitHub Release. Installing from it needs an `.npmrc` and a token even though it is public, so npmjs.com is the easier path. |
460
467
  | The version line at the top, and the version inside the `$id` | May lag the latest tag. `CHANGELOG.md` is authoritative. |
461
- | `npx @open-predicate/open-predicate` | Not reachable from a registry until the bootstrap in the row above has run. The `bin` entry is real and the tool ships inside the package, so this works from a clone or a git install either way. |
468
+ | `npx @open-predicate/open-predicate` | Works from `0.6.1`. **Broken in `0.6.0`**, where the `bin` exited 0 having printed nothing — the entry guard compared `process.argv[1]` against `import.meta.url`, and npm installs a `bin` as a *symlink*. Use the package name, not the bin name `open-predicate-generate`, which is not a package. |
462
469
 
463
- Serving the schema at its `$id` and publishing the package are the remaining work. Until then the only fetchable copy of the schema is raw GitHub:
470
+ **The `$id` resolves.** The schema is served from the URL that identifies it, so the workflow the OpenAPI sections describe is a description of today rather than an intended end state:
464
471
 
465
472
  ```bash
466
- curl -O https://raw.githubusercontent.com/OpenPredicate/open-predicate/main/open-predicate-schema.json
473
+ curl -sI https://openpredicate.tech/schema/v0.4.0/open-predicate-schema.json
474
+ # content-type: application/schema+json; charset=utf-8
475
+ # access-control-allow-origin: *
476
+ # cache-control: public,max-age=31536000,immutable
467
477
  ```
468
478
 
469
- Vendor that file rather than referencing it remotely, and treat the resolvable-`$id` workflow the OpenAPI sections describe as the intended end state rather than a description of today.
479
+ Those three headers are the ones that matter: the media type so validators recognise it, the CORS header so browser-based tooling can fetch it, and immutable caching because a versioned `$id` never changes (§9). `$ref` it by URL and you are done.
480
+
481
+ A local copy is still the right choice for air-gapped builds, for pinning independently of DNS, or for the bundle-and-narrow workflow in [Restricting the queryable field set](#restricting-the-queryable-field-set). Either get it from the package:
482
+
483
+ ```bash
484
+ npm i @open-predicate/open-predicate
485
+ # -> node_modules/@open-predicate/open-predicate/open-predicate-schema.json
486
+ ```
487
+
488
+ or with no package manager in the way — pinning a tag rather than `main`:
489
+
490
+ ```bash
491
+ curl -O https://raw.githubusercontent.com/OpenPredicate/open-predicate/v0.6.1/open-predicate-schema.json
492
+ ```
493
+
494
+ The served copy and the packaged copy are the same bytes: the site vendors the schema out of this repository at a tag, so a release moves both together. See [`RELEASING.md`](./RELEASING.md).
470
495
 
471
496
  **What is stable enough to review.** The grammar, the operator set and profile grouping, the null and three-valued semantics, and the error model. Those are what the schema, [`SPEC.md`](./SPEC.md) and the test suite pin down, and they are what feedback is most useful on. The grammar may still change before 1.0; each break is recorded in [`CHANGELOG.md`](./CHANGELOG.md) with a migration note.
472
497
 
@@ -476,7 +501,11 @@ OpenPredicate is stewarded by the [**OpenPredicate**](https://openpredicate.tech
476
501
 
477
502
  That goal sets the terms of the work. The grammar is specified normatively in [`SPEC.md`](./SPEC.md) rather than left to a reference implementation, so that independent implementations can agree; every breaking change is recorded with a migration note; and each significant design decision is argued in writing under [`decisions/`](./decisions) rather than settled by commit. Adoption arguments belong in the open too — [`COMPARISON.md`](./COMPARISON.md) is where the case against the nearest alternative is made and its gaps admitted.
478
503
 
479
- The specification and this repository are [MIT](./LICENSE)-licensed, so the grammar can be implemented, vendored, extended and re-specified without permission. Issues and design discussion are welcome at [github.com/OpenPredicate/open-predicate](https://github.com/OpenPredicate/open-predicate/issues).
504
+ [`GOVERNANCE.md`](./GOVERNANCE.md) is where that process is written down: what each class of change costs, how a disputed design call is resolved — including that dissent is recorded in the decision record rather than discarded — what earns commit rights, and the project's royalty-free patent posture. It also states plainly that there is currently one maintainer, and treats that as the defect it is.
505
+
506
+ The specification and this repository are [MIT](./LICENSE)-licensed, so the grammar can be implemented, vendored, extended and re-specified without permission, commercially or otherwise.
507
+
508
+ **Disagreement is the most useful contribution at this stage**, and [`CONTRIBUTING.md`](./CONTRIBUTING.md) says how to make it: file a design objection, build an implementation (or help turn the fixtures into a real conformance suite), or claim conformance for a library you already maintain. Open-ended design talk belongs in [Discussions](https://github.com/OpenPredicate/open-predicate/discussions); a defect or an objection belongs in an [issue](https://github.com/OpenPredicate/open-predicate/issues/new/choose).
480
509
 
481
510
  ## License
482
511
 
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@open-predicate/open-predicate",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "OpenPredicate — an open standard for JSON-encoded, SQL-flavoured predicates, described by a single JSON Schema: $ref it from OpenAPI, or inline it into an MCP tool's inputSchema.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
+ "engines": {
8
+ "node": ">=20.10.0"
9
+ },
7
10
  "author": "Christos Gkoros",
8
11
  "contributors": [
9
12
  "OpenPredicate (https://openpredicate.tech)"
@@ -67,7 +67,7 @@
67
67
  * "x-open-predicate": { "operators": ["$eq"] } — exactly these operators
68
68
  */
69
69
 
70
- import { readFileSync, writeFileSync } from "node:fs";
70
+ import { readFileSync, realpathSync, writeFileSync } from "node:fs";
71
71
  import { fileURLToPath } from "node:url";
72
72
  import { dirname, join, resolve } from "node:path";
73
73
  import { parseArgs } from "node:util";
@@ -1148,6 +1148,24 @@ function main(argv) {
1148
1148
  }
1149
1149
  }
1150
1150
 
1151
- if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
1151
+ // Run only when invoked as a program, not when imported. `process.argv[1]` is
1152
+ // compared through realpathSync because npm installs a `bin` as a *symlink* —
1153
+ // argv[1] is then `node_modules/.bin/open-predicate-generate` while
1154
+ // import.meta.url is the file it points at, so a direct comparison is always
1155
+ // false and the CLI exits 0 having done nothing. That is how `npx` invocation
1156
+ // silently no-opped while `node tools/generate-filter-schema.mjs` worked.
1157
+ function invokedAsProgram() {
1158
+ const entry = process.argv[1];
1159
+ if (!entry) return false;
1160
+ const self = fileURLToPath(import.meta.url);
1161
+ if (entry === self) return true;
1162
+ try {
1163
+ return realpathSync(entry) === realpathSync(self);
1164
+ } catch {
1165
+ return false;
1166
+ }
1167
+ }
1168
+
1169
+ if (invokedAsProgram()) {
1152
1170
  main(process.argv.slice(2));
1153
1171
  }