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
5 changes: 5 additions & 0 deletions .changeset/feature-query-endpoints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hypercerts-org/hypercerts-api': minor
---

Add `org.hypercerts.entity.getFeature` to retrieve an indexed feature by its exact record URI, with available author profile and organization details. Add `org.hypercerts.entity.listFeatures` to filter indexed features by author, type, and organization-record presence, and page results by creation time and URI.
5 changes: 3 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pnpm build

## HTTP runtime tests

`pnpm test:http` runs the suites in `api/tests/http` against the activity, badge-definition, badge-query, acknowledgement, collection, context attachment and evaluation, contributor-information, funding, location, profile, organization, work-scope-tag, vocabulary-tag, graph, and contribution query XRPC endpoints installed from this checkout. These tests exercise real HTTP behavior against PostgreSQL, not only Lua handlers with a fake database.
`pnpm test:http` runs the suites in `api/tests/http` against the activity, badge-definition, badge-query, acknowledgement, collection, context attachment and evaluation, contributor-information, funding, location, profile, organization, feature, work-scope-tag, vocabulary-tag, graph, and contribution query XRPC endpoints installed from this checkout. These tests exercise real HTTP behavior against PostgreSQL, not only Lua handlers with a fake database.

The local runner requires a local Docker Compose daemon, `psql`, and the pinned PostgreSQL and HappyView images already cached locally. Set `PSQL_PATH` to the absolute path of a trusted `psql` executable:

Expand All @@ -33,6 +33,7 @@ The HTTP gate fails if it discovers no suites, executes no `node:test` cases, or

- Funding record retrieval, repeated filters, and pagination.
- Badge-definition retrieval with an icon and allowed-issuer list, publisher-sidecar hydration, author and badge-type filters, `createdAt`/URI pagination ties, and named error responses.
- Feature-query coverage in `api/tests/http/features.http.test.js` exercises exact retrieval and hydrated or absent author sidecars, author/type and organization-presence filters, bidirectional `createdAt`/URI pagination ties, and named request errors.
- Contributor-information retrieval by exact AT-URI and listing with repeated-author filters, cursor pagination, hydrated and missing author sidecars, and named runtime errors.
- Work-scope-tag exact-URI retrieval, repeated-author filtering with an empty-result case, hydrated and null publisher sidecars, middle-position `indexedAt` fallback, tied pagination in both directions, and named error responses.
- Acknowledgement exact retrieval and full-record preservation, hydrated and absent publisher sidecars, repeated author/subject filters, ascending and descending pagination across timestamp ties, and named errors.
Expand All @@ -46,7 +47,7 @@ The HTTP gate fails if it discovers no suites, executes no `node:test` cases, or
- Contribution exact-record retrieval, repeated publisher filters, tied ascending/descending cursor pagination, nullable publisher sidecars, and named errors.
- Badge and contribution fixtures with CBOR-derived record CIDs; contribution DIDs are distinct from baseline fixture identities.
- Location retrieval with nullable sidecars, repeated author/URI/location-type filters, unsupported-search errors, tied pagination in both directions, malformed/absent `createdAt` handling, and named errors.
- HTTP fixtures for activity, collection, funding, badge-definition, badge-query, acknowledgement, work-scope-tag, contribution, contributor-information, actor, context attachment and evaluation, graph, location, and vocabulary-tag records use CBOR-derived CIDs.
- HTTP fixtures for activity, collection, funding, badge-definition, badge-query, acknowledgement, feature, work-scope-tag, contribution, contributor-information, actor, context attachment and evaluation, graph, location, and vocabulary-tag records use CBOR-derived CIDs.

For the pinned HappyView release, ordinary Lua `error()` exceptions return HTTP 500 JSON with `error: "script_error"` and `errorType: "runtime"`; the error name appears in `message`. Negative HTTP tests assert this observed behavior. It is not a statement of the ideal public HTTP status contract, and does not guarantee 4xx mapping for `RecordNotFound` or `InvalidRequest`.

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Hypercerts API workspace

This repository combines the shared HappyView installer, pinned Lexicon dependencies, reusable Lua projections, fixtures, offline checks, and independently owned capability modules. This composed checkout includes public query modules for actor profiles and organizations, activity, badge definitions and queries, collections, context attachments and evaluations, actor and entity follows, recent follows, funding receipts, locations, work-scope tags, contribution records, contributor information, vocabulary tags, and acknowledgements. The `org.hypercerts.vocab.getVocabTag` and `org.hypercerts.vocab.listVocabTags` handlers are bundled and registered through `api/modules/vocab/manifest.json`.
This repository combines the shared HappyView installer, pinned Lexicon dependencies, reusable Lua projections, fixtures, offline checks, and independently owned capability modules. This composed checkout includes public query modules for actor profiles and organizations, activity, badge definitions and queries, collections, context attachments and evaluations, actor and entity follows, recent follows, funding receipts, locations, features, work-scope tags, contribution records, contributor information, vocabulary tags, and acknowledgements. The `org.hypercerts.vocab.getVocabTag` and `org.hypercerts.vocab.listVocabTags` handlers are bundled and registered through `api/modules/vocab/manifest.json`.

This checkout is one of the additive local sibling branches used to compose the API:

Expand Down Expand Up @@ -33,6 +33,7 @@ pnpm build
| Funding receipts | `org.hypercerts.funding.getReceipt`, `org.hypercerts.funding.listReceipts` | Record retrieval, repeated filters, stable pagination, and named runtime errors |
| Badge definitions | `app.certified.badge.getBadgeDefinition`, `app.certified.badge.listBadgeDefinitions` | Record/CID retrieval, publisher sidecars, filters, tied pagination, and named runtime errors |
| Badge queries | `app.certified.badge.searchBadgeDefinitions`, `app.certified.badge.getBadgeAward`, `app.certified.badge.listBadgeAwards`, `app.certified.badge.getBadgeResponse`, `app.certified.badge.listBadgeResponses` | Baseline-aware definition search, exact-version award/response lookups, recipient status, raw response history, filters, and cursor pagination |
| Features | `org.hypercerts.entity.getFeature`, `org.hypercerts.entity.listFeatures` | Exact retrieval, author sidecars, author/type and organization-presence filters, tied pagination, and named errors |
| Contributor information | `org.hypercerts.claim.getContributorInformation`, `org.hypercerts.claim.listContributorInformation` | Exact-URI retrieval, repeated-author filters, cursor pagination, hydrated and missing author sidecars, and named runtime errors |
| Work-scope tags | `org.hypercerts.workscope.getWorkscopeTag`, `org.hypercerts.workscope.listWorkscopeTags` | Exact-URI retrieval, author filters, hydrated and null sidecars, middle-position `indexedAt` fallback, tied pagination in both directions, and named runtime errors |
| Contributions | `org.hypercerts.claim.getContribution`, `org.hypercerts.claim.listContributions` | Exact-record retrieval, publisher filters, hydrated and null sidecars, createdAt/indexedAt fallback, tied pagination in both directions, and named runtime errors |
Expand Down
38 changes: 36 additions & 2 deletions api/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,40 @@
# HappyView API toolkit foundation
# HappyView API toolkit and query modules

This package owns the shared API installer and build tooling, pinned upstream Lexicons, common view definitions, reusable Lua projections, and offline fixture/test utilities. The `modules/shared/manifest.json` contains record schemas and query Lexicons used as shared view types; it contains no Lua endpoint scripts. A foundation-only install therefore does not implement those queries. Capability modules listed in the root manifest register their endpoint Lexicons and handlers separately. The `workscope-tags` module adds public lookup and listing queries for indexed `org.hypercerts.workscope.tag` records; the `contribution` module adds public queries for contribution records; the `badge-queries` module installs five public badge query Lexicons and their Lua handlers.
This package owns the shared API installer and build tooling, pinned upstream Lexicons, common view definitions, reusable Lua projections, offline fixture/test utilities, and the feature, contribution, badge-query, vocabulary-tag, and acknowledgement modules. The `modules/shared/manifest.json` contains record schemas and shared query/view Lexicons; it contains no Lua endpoint scripts. A foundation-only install does not implement those queries. Capability modules listed in the root manifest register their endpoint Lexicons and Lua handlers separately; the `badge-queries` module installs five public badge query Lexicons and their Lua handlers.

## Local checks

From the repository root:

```sh
pnpm install --frozen-lockfile
pnpm test:unit
pnpm check
pnpm build
PSQL_PATH="$(command -v psql)" pnpm test:http
```

`pnpm test:unit` and `pnpm check` run the recursively discovered tests under `api/tests/unit`; unit tests do not require HappyView or PostgreSQL. `pnpm check` also validates generated-source freshness, JavaScript/Lua lint, and types. `pnpm build` emits only Lua handlers declared by the root and module manifests. Shared Lua files are bundled into capability handlers but are not installed independently.

`pnpm test:http` requires a local Docker Compose daemon, `psql`, and the pinned PostgreSQL and HappyView images already present in the local image cache. The local runner never pulls images. The CI workflow explicitly pulls only the two digest-pinned test images before running the same command. Set `PSQL_PATH` to the absolute path returned by `command -v psql`.

## HTTP runtime tests

HTTP suites live in `api/tests/http` and exercise the funding, badge-definition, badge-query, feature, work-scope-tag, contribution, profile, organization, context attachment and evaluation, activity, collection, acknowledgement, vocabulary-tag, graph, and location XRPC endpoints installed from the current checkout. `pnpm test:http` discovers `*.http.test.js` suites and fixture modules named `*.fixture.js`, then creates a random Compose project with loopback-only dynamic ports, PostgreSQL data on tmpfs, and a task-owned default bridge network. Bridge networking permits container egress. HappyView receives loopback placeholder upstream URLs and proxy variables pointing to `127.0.0.1:9`; these are application-level settings, not network-hard egress isolation. The installer and HTTP suites target only the task-owned loopback service. The runner installs this checkout's manifest, seeds shared and HTTP fixtures, runs the suites, and tears down only that generated Compose project and its temporary credentials. Locally, Compose uses `--pull never`; missing cached images fail before service startup.

The HTTP gate fails when it discovers zero suites, executes zero `node:test` cases, or runs only skipped cases. These checks cover real HTTP behavior against PostgreSQL, not just Lua handlers with a fake database. Funding coverage exercises record retrieval, repeated filters, and pagination. Badge-definition coverage exercises retrieval with an icon and allowed-issuer list, publisher-sidecar hydration, author and badge-type filters, createdAt/URI pagination ties, and named error responses. Badge-query coverage exercises baseline-aware definition feeds and discriminating filters, exact-version award/response lookups, recipient status, raw response history, bidirectional tied pagination, nullable sidecars, and named runtime errors. Vocabulary-tag coverage exercises exact retrieval, author filters, hydrated and nullable sidecars, tied pagination, and named errors. Acknowledgement coverage exercises exact retrieval, hydrated and absent publisher sidecars, repeated author/subject filters, tied pagination in both directions, and named errors. Feature coverage exercises exact retrieval and author hydration, list filters and sidecars, tied createdAt/URI pagination, and named errors. Contribution coverage exercises exact-record retrieval, repeated publisher filters, tied ascending/descending cursor pagination, nullable publisher sidecars, and named errors. Fixtures use CBOR-derived record CIDs and are seeded only into the task-owned disposable database.

For the pinned HappyView release, ordinary Lua `error()` exceptions are returned as HTTP 500 JSON with `error: "script_error"` and `errorType: "runtime"`; the error name appears in `message`. The negative HTTP tests assert this observed runtime behavior. They do not define an ideal public HTTP status contract or guarantee 4xx mapping for `RecordNotFound` and `InvalidRequest`.

## Feature query API

Both feature queries are public and require no authentication. The aggregate manifest includes the feature module and its validation Lexicons; the handlers read indexed records from PostgreSQL `happyview_records`.

`org.hypercerts.entity.getFeature` accepts the exact feature record AT-URI, including its DID authority and record key. It returns `InvalidRequest` for malformed or non-feature URIs and `RecordNotFound` when that exact URI is not indexed. The `FeatureView` preserves the indexed record and hydrates only the author's profile and organization sidecar; either actor record may be null, and feature locations, tags, and `sameAs` references remain unexpanded.

`org.hypercerts.entity.listFeatures` accepts repeated, unbracketed `authors` and `types` query keys, with at most 100 values per array. Different filters combine with AND, while values within either array combine with OR. Authors are repository-owner DIDs; types are exact, case-sensitive open strings of at most 64 UTF-8 bytes. `hasOrganizationRecord=true` requires an `app.certified.actor.organization/self` record, while `false` matches its absence regardless of profile presence.

Listings sort by `(createdAt, uri)` in the requested direction, defaulting to descending. Pages default to 25 entries and accept limits from 1 through 100. The opaque cursor is bound to `sortDirection`; reuse the same filters when continuing a listing. The response omits `cursor` after the final page. Unknown parameters, repeated scalar parameters, malformed filters, out-of-range limits, and invalid or direction-mismatched cursors return `InvalidRequest`.

## Work-scope tag queries

Expand Down
21 changes: 21 additions & 0 deletions api/lexicons/org.hypercerts.entity.defs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"lexicon": 1,
"id": "org.hypercerts.entity.defs",
"description": "Shared definitions for Hypercerts entity queries.",
"defs": {
"featureView": {
"type": "object",
"description": "Indexed feature record with its hydrated author actor.",
"required": ["uri", "cid", "indexedAt", "did", "author", "record"],
"nullable": ["indexedAt"],
"properties": {
"uri": { "type": "string", "format": "at-uri" },
"cid": { "type": "string", "format": "cid" },
"indexedAt": { "type": "string", "format": "datetime" },
"did": { "type": "string", "format": "did" },
"author": { "type": "ref", "ref": "org.hypercerts.api.defs#actorView" },
"record": { "type": "ref", "ref": "org.hypercerts.entity.feature" }
}
}
}
}
36 changes: 36 additions & 0 deletions api/lexicons/org.hypercerts.entity.getFeature.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"lexicon": 1,
"id": "org.hypercerts.entity.getFeature",
"defs": {
"main": {
"type": "query",
"description": "Looks up one indexed feature by exact AT-URI with author hydration; authentication is not required.",
"parameters": {
"type": "params",
"required": ["uri"],
"properties": {
"uri": {
"type": "string",
"format": "at-uri",
"description": "Full feature record AT-URI using a DID authority."
}
}
},
"output": {
"encoding": "application/json",
"schema": { "type": "ref", "ref": "#output" }
},
"errors": [
{ "name": "InvalidRequest", "description": "The URI is invalid or is not a feature record AT-URI." },
{ "name": "RecordNotFound", "description": "No indexed feature exists at this AT-URI." }
]
},
"output": {
"type": "object",
"required": ["feature"],
"properties": {
"feature": { "type": "ref", "ref": "org.hypercerts.entity.defs#featureView" }
}
}
}
}
74 changes: 74 additions & 0 deletions api/lexicons/org.hypercerts.entity.listFeatures.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{
"lexicon": 1,
"id": "org.hypercerts.entity.listFeatures",
"defs": {
"main": {
"type": "query",
"description": "Lists indexed features with filters and direction-bound pagination; authentication is not required.",
"parameters": {
"type": "params",
"properties": {
"authors": {
"type": "array",
"maxLength": 100,
"description": "Repository-owner DIDs; values combine with OR.",
"items": { "type": "string", "format": "did" }
},
"hasOrganizationRecord": {
"type": "boolean",
"description": "Whether the author has an organization self record; false is independent of profile presence."
},
"types": {
"type": "array",
"maxLength": 100,
"description": "Exact open-string feature types; values combine with OR.",
"items": {
"type": "string",
"maxLength": 64,
"description": "Exact case-sensitive feature type."
}
},
"sortDirection": {
"type": "string",
"enum": ["asc", "desc"],
"default": "desc",
"description": "Direction for sorting by createdAt and URI."
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 25,
"description": "Maximum number of features in the page."
},
"cursor": {
"type": "string",
"maxLength": 32768,
"description": "Opaque cursor from the previous page, bound to sortDirection."
}
}
},
"output": {
"encoding": "application/json",
"schema": { "type": "ref", "ref": "#output" }
},
"errors": [
{ "name": "InvalidRequest", "description": "A filter, page bound, cursor, repeated scalar, or query parameter is invalid." }
]
},
"output": {
"type": "object",
"required": ["features"],
"properties": {
"features": {
"type": "array",
"items": { "type": "ref", "ref": "org.hypercerts.entity.defs#featureView" }
},
"cursor": {
"type": "string",
"description": "Opaque cursor for the next page; omitted when there is no next page."
}
}
}
}
}
14 changes: 8 additions & 6 deletions api/lua/endpoints/getBadgeDefinition.lua
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ local function valid_record_uri(value)
return true, collection, authority
end

local BADGE_DEFINITION_COLLECTION = "app.certified.badge.definition"

local function valid_badge_definition_uri(value)
local valid, collection = valid_record_uri(value)
return valid and collection == BADGE_DEFINITION_COLLECTION
end

local NULL = json.decode("null")

local function record_view(row)
Expand Down Expand Up @@ -81,12 +88,7 @@ local function hydrate_actor_views(actors, run_query)
end
end

local COLLECTION = "app.certified.badge.definition"

local function valid_badge_definition_uri(value)
local valid, collection = valid_record_uri(value)
return valid and collection == COLLECTION
end
local COLLECTION = BADGE_DEFINITION_COLLECTION

local function query(sql, values)
local ok, result = pcall(db.raw, sql, values)
Expand Down
Loading
Loading