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
4 changes: 3 additions & 1 deletion .agents/skills/hypercerts-api/references/endpoint-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This task index lists the public query NSIDs in the bundled [OpenAPI contract](o
- **Text discovery:** use `search...` only where listed below. A search query is not interchangeable with a list filter, and search semantics vary by endpoint.
- **Relationship checks:** use `get...Follow` for a single pair, or `getFollows` / `getEntityFollows` to check repeated target values for one actor. List queries answer who follows whom or which entities an actor follows.
- **Feed reactions:** use `getLikes` or `getReposts` for distinct actors reacting to one subject URI, `getActorLikes` / `getActorReposts` for one actor's raw representative reactions, `getActorReactions` to check one actor across a bounded set of known subject URIs, or `getReactionCounts` for indexed like and repost counts across known subjects.
- **Comment counts:** use `org.hypercerts.context.getCommentCounts` for comment counts across known subjects. Comments are attachments, not feed reactions; list them with `listAttachments` filtered by `subjects` and `contentTypes=comment`.

Inspect the exact operation in the bundled OpenAPI contract before constructing a request: names such as `authors`, `actors`, `uri`, and `collection` have endpoint-specific meanings. Do not infer that a filter, search operation, output array name, or error exists just because a related endpoint has one.

Expand Down Expand Up @@ -98,14 +99,15 @@ Do not confuse actor follows with entity follows; their identifiers and list out

Paginated reaction-list queries use the earliest record per author/subject URI, with reaction URI ascending on ties, and sort pages by the representative's `createdAt` and URI. `getActorReactions` uses the same representative selection but preserves request order and has no pagination. Check each operation for exact cursor and response details.

## Context and supporting records (8)
## Context and supporting records (9)

| User task | Query NSID | Practical choice |
|---|---|---|
| Load one acknowledgement | `org.hypercerts.context.getAcknowledgement` | Exact record AT-URI in `uri`. |
| List acknowledgements | `org.hypercerts.context.listAcknowledgements` | Filter by publisher or subject as documented. |
| Load one attachment | `org.hypercerts.context.getAttachment` | Exact record AT-URI in `uri`. |
| List attachments | `org.hypercerts.context.listAttachments` | Filter by publisher, record URI, subject, or content type. |
| Count comments for subjects | `org.hypercerts.context.getCommentCounts` | Pass 1–100 full subject record AT-URIs as repeated, unbracketed `subjects`. Counts attachments with `contentType` `comment` whose `subjects` include the URI, ignoring CID. Each comment record counts, so several by one author each count. Results preserve input order and duplicates. |
| Load one evaluation | `org.hypercerts.context.getEvaluation` | Exact record AT-URI in `uri`; includes publisher/evaluator projections and preserves raw records when malformed evaluator entries are omitted. |
| List evaluations | `org.hypercerts.context.listEvaluations` | Filter by publisher, evaluator, or subject; malformed evaluator entries do not hide otherwise valid records. |
| Load one measurement | `org.hypercerts.context.getMeasurement` | Exact record AT-URI in `uri`. |
Expand Down
79 changes: 79 additions & 0 deletions .agents/skills/hypercerts-api/references/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -3071,6 +3071,56 @@
]
}
},
"/xrpc/org.hypercerts.context.getCommentCounts": {
"get": {
"operationId": "org_hypercerts_context_getCommentCounts",
"tags": ["org.hypercerts.context"],
"description": "Returns indexed comment counts for a list of subject records, with no authentication required. A comment is an org.hypercerts.context.attachment with contentType \"comment\" whose subjects include the record; every comment record counts, even several by the same author, and matching uses subjects[].uri, ignoring CID and whether the subject is indexed.",
"parameters": [
{
"name": "subjects",
"in": "query",
"required": true,
"description": "Pass 1–100 full DID-authority subject record AT-URIs as repeated, unbracketed subjects query parameters. The encoded GET request-target must not exceed 7,000 bytes.",
"schema": {
"type": "array",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"format": "at-uri",
"maxLength": 8192
}
},
"style": "form",
"explode": true
}
],
"responses": {
"200": {
"description": "Successful response.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/org.hypercerts.context.getCommentCounts.output"
}
}
}
}
},
"x-lexicon-id": "org.hypercerts.context.getCommentCounts",
"x-lexicon-errors": [
{
"name": "InvalidRequest",
"description": "The subjects list is empty, has more than 100 entries, exceeds the request-target limit, or contains an invalid subject AT-URI; correct the list and retry."
},
{
"name": "CommentCountQueryFailed",
"description": "The indexed comment count query failed; retry the request."
}
]
}
},
"/xrpc/org.hypercerts.context.getEvaluation": {
"get": {
"operationId": "org_hypercerts_context_getEvaluation",
Expand Down Expand Up @@ -5726,6 +5776,35 @@
"type": "record",
"description": "An evaluation of a hypercert record (e.g. an activity and its impact)."
},
"org.hypercerts.context.getCommentCounts.subjectCommentCount": {
"type": "object",
"required": ["subject", "commentCount"],
"properties": {
"subject": {
"type": "string",
"description": "Subject AT-URI requested by the caller; matching ignores the subject CID.",
"format": "at-uri"
},
"commentCount": {
"type": "integer",
"description": "Number of current indexed comment attachments whose subjects include this URI; zero when none are indexed.",
"minimum": 0
}
}
},
"org.hypercerts.context.getCommentCounts.output": {
"type": "object",
"required": ["counts"],
"properties": {
"counts": {
"type": "array",
"description": "One entry for each requested subject, in request order. Repeated input URIs produce repeated entries. Counts reflect current indexed state and may change between requests.",
"items": {
"$ref": "#/components/schemas/org.hypercerts.context.getCommentCounts.subjectCommentCount"
}
}
}
},
"org.hypercerts.context.getEvaluation.hydratedEvaluationEvaluatorView": {
"type": "object",
"description": "A valid original evaluator entry with its profile and organization sidecars looked up.",
Expand Down
2 changes: 2 additions & 0 deletions .agents/skills/hypercerts-api/references/request-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ const url = new URL(
The response contains one `results` item per supplied occurrence in that same order. Each item repeats the explicit `subject` AT-URI and has `like` and `repost` fields that are either raw record views or null. Matching ignores the subject CID. A null means the successful indexed lookup found no such reaction; a query failure is an error, not null. Preserve a non-null view's `uri` and `cid` when you need to identify that reaction record elsewhere.
`app.certified.feed.getReactionCounts` accepts 1–100 full subject record AT-URIs with DID authorities, not handle authorities. Append each value under the unbracketed `subjects` key; do not use `subjects[]` or comma-join values. Its response has one entry per input occurrence in the same order, including duplicates. A zero count means no matching indexed reaction publishers were found at query time; counts may change as indexing changes.

`org.hypercerts.context.getCommentCounts` takes the same `subjects` encoding: 1–100 full subject record AT-URIs with DID authorities, appended under the unbracketed `subjects` key, within a 7,000-byte encoded request-target. Each `{subject, commentCount}` entry follows input order, including duplicates. The count is the number of current comment attachment records, not distinct commenters, and zero means none were indexed at query time.

## Keep page requests cursor-consistent

For a list/search operation whose output has a cursor, keep a base set of all non-cursor parameters and replace only the cursor on continuation. Preserve repeated array values, filters, `search`, `limit`, and `sortDirection` exactly. The example below is illustrative; verify the chosen endpoint's cursor binding and response-array name.
Expand Down
8 changes: 8 additions & 0 deletions .changeset/batch-comment-counts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@hypercerts-org/happyview-modules': minor
'@hypercerts-org/api': minor
---

Add `org.hypercerts.context.getCommentCounts`, which returns comment counts for up to 100 subject records in one call.

`getEntityFollows` and `getActorReactions` now reject subject URIs whose DID has a malformed `%` escape, as `getReactionCounts` already did, instead of returning null for them.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ The HTTP gate fails if it discovers no suites, executes no `node:test` cases, or
- Badge-query 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.
- Profile and organization queries across all four endpoints for each record type, including batch null results, profile-sidecar hydration, filters, `createdAt`/URI pagination ties, and named errors.
- Vocabulary-tag exact-URI retrieval, repeated-author filtering and no-match behavior, hydrated and nullable sidecars, bidirectional pagination ties, unsupported-search rejection, and named errors.
- Context attachment and evaluation retrieval with publisher/evaluator sidecar hydration, list filters, pagination across tied `createdAt` values, and named runtime errors.
- Context attachment and evaluation retrieval with publisher/evaluator sidecar hydration, list filters, pagination across tied `createdAt` values, and named runtime errors. Batched comment counts cover per-record counting, CID-independent subject matching, excluded non-comment attachments, and agreement with `listAttachments`.
- Activity retrieval with contributor-sidecar hydration, author/organization/contributor/URI filters, tied timestamp pagination, and literal wildcard search.
- Collection retrieval with CBOR-derived CIDs, location/tag projections, author, organization, item and tag filters, title/shortDescription search, `createdAt`/URI pagination ties, and source-order item pagination with exact-version resolution.
- Graph actor/entity lookups and lists, tied-key pagination, nullable profile/organization sidecars, entity target resolution, entity-follower totals across pages and empty pages, and the global recent-follows `before` filter. The `getFollows` and `getEntityFollows` batch queries also cover repeated target order and duplicates, explicit target keys, nullable raw views, and invalid-entry rejection.
Expand Down
12 changes: 9 additions & 3 deletions docs/coverage.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
},
"runtimeValidation": "not-assessed",
"deploymentValidation": "not-assessed",
"endpointCount": 63,
"endpointCount": 64,
"methods": {
"GET": 63
"GET": 64
},
"inclusion": {
"manifest-registered": 63
"manifest-registered": 64
},
"unresolvedReferences": [],
"endpoints": [
Expand Down Expand Up @@ -308,6 +308,12 @@
"inclusion": "manifest-registered",
"module": "modules/context-attachments/manifest.json"
},
{
"id": "org.hypercerts.context.getCommentCounts",
"method": "GET",
"inclusion": "manifest-registered",
"module": "modules/context-attachments/manifest.json"
},
{
"id": "org.hypercerts.context.getEvaluation",
"method": "GET",
Expand Down
90 changes: 90 additions & 0 deletions docs/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -3933,6 +3933,62 @@
]
}
},
"/xrpc/org.hypercerts.context.getCommentCounts": {
"get": {
"operationId": "org_hypercerts_context_getCommentCounts",
"tags": [
"org.hypercerts.context"
],
"description": "Returns indexed comment counts for a list of subject records, with no authentication required. A comment is an org.hypercerts.context.attachment with contentType \"comment\" whose subjects include the record; every comment record counts, even several by the same author, and matching uses subjects[].uri, ignoring CID and whether the subject is indexed.",
"parameters": [
{
"name": "subjects",
"in": "query",
"required": true,
"description": "Pass 1–100 full DID-authority subject record AT-URIs as repeated, unbracketed subjects query parameters. The encoded GET request-target must not exceed 7,000 bytes.",
"schema": {
"type": "array",
"minItems": 1,
"maxItems": 100,
"items": {
"type": "string",
"format": "at-uri",
"maxLength": 8192
}
},
"style": "form",
"explode": true
}
],
"responses": {
"200": {
"description": "Successful response.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/org.hypercerts.context.getCommentCounts.output"
}
}
}
}
},
"x-lexicon-id": "org.hypercerts.context.getCommentCounts",
"x-hypercerts-coverage": "manifest-registered",
"x-hypercerts-source": {
"module": "modules/context-attachments/manifest.json"
},
"x-lexicon-errors": [
{
"name": "InvalidRequest",
"description": "The subjects list is empty, has more than 100 entries, exceeds the request-target limit, or contains an invalid subject AT-URI; correct the list and retry."
},
{
"name": "CommentCountQueryFailed",
"description": "The indexed comment count query failed; retry the request."
}
]
}
},
"/xrpc/org.hypercerts.context.getEvaluation": {
"get": {
"operationId": "org_hypercerts_context_getEvaluation",
Expand Down Expand Up @@ -7943,6 +7999,40 @@
}
}
},
"org.hypercerts.context.getCommentCounts.subjectCommentCount": {
"type": "object",
"required": [
"subject",
"commentCount"
],
"properties": {
"subject": {
"type": "string",
"description": "Subject AT-URI requested by the caller; matching ignores the subject CID.",
"format": "at-uri"
},
"commentCount": {
"type": "integer",
"description": "Number of current indexed comment attachments whose subjects include this URI; zero when none are indexed.",
"minimum": 0
}
}
},
"org.hypercerts.context.getCommentCounts.output": {
"type": "object",
"required": [
"counts"
],
"properties": {
"counts": {
"type": "array",
"description": "One entry for each requested subject, in request order. Repeated input URIs produce repeated entries. Counts reflect current indexed state and may change between requests.",
"items": {
"$ref": "#/components/schemas/org.hypercerts.context.getCommentCounts.subjectCommentCount"
}
}
}
},
"org.hypercerts.context.evaluation.main": {
"type": "record",
"description": "An evaluation of a hypercert record (e.g. an activity and its impact)."
Expand Down
18 changes: 18 additions & 0 deletions docs/sources/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,15 @@
"path": "../../lexicons/org.hypercerts.context.getAttachment.json"
}
},
{
"id": "org.hypercerts.context.getCommentCounts",
"type": "query",
"module": "modules/context-attachments/manifest.json",
"file": "sources/lexicons/org.hypercerts.context.getCommentCounts.json",
"source": {
"path": "../../lexicons/org.hypercerts.context.getCommentCounts.json"
}
},
{
"id": "org.hypercerts.context.getEvaluation",
"type": "query",
Expand Down Expand Up @@ -1207,6 +1216,15 @@
"path": "../../lexicons/org.hypercerts.context.getAttachment.json"
}
},
{
"id": "org.hypercerts.context.getCommentCounts",
"file": "sources/lexicons/org.hypercerts.context.getCommentCounts.json",
"kind": "endpoint",
"source": {
"module": "modules/context-attachments/manifest.json",
"path": "../../lexicons/org.hypercerts.context.getCommentCounts.json"
}
},
{
"id": "org.hypercerts.context.getEvaluation",
"file": "sources/lexicons/org.hypercerts.context.getEvaluation.json",
Expand Down
81 changes: 81 additions & 0 deletions docs/sources/lexicons/org.hypercerts.context.getCommentCounts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
{
"lexicon": 1,
"id": "org.hypercerts.context.getCommentCounts",
"defs": {
"main": {
"type": "query",
"description": "Returns indexed comment counts for a list of subject records, with no authentication required. A comment is an org.hypercerts.context.attachment with contentType \"comment\" whose subjects include the record; every comment record counts, even several by the same author, and matching uses subjects[].uri, ignoring CID and whether the subject is indexed.",
"parameters": {
"type": "params",
"required": [
"subjects"
],
"properties": {
"subjects": {
"type": "array",
"minLength": 1,
"maxLength": 100,
"description": "Pass 1–100 full DID-authority subject record AT-URIs as repeated, unbracketed subjects query parameters. The encoded GET request-target must not exceed 7,000 bytes.",
"items": {
"type": "string",
"format": "at-uri",
"maxLength": 8192
}
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "ref",
"ref": "#output"
}
},
"errors": [
{
"name": "InvalidRequest",
"description": "The subjects list is empty, has more than 100 entries, exceeds the request-target limit, or contains an invalid subject AT-URI; correct the list and retry."
},
{
"name": "CommentCountQueryFailed",
"description": "The indexed comment count query failed; retry the request."
}
]
},
"subjectCommentCount": {
"type": "object",
"required": [
"subject",
"commentCount"
],
"properties": {
"subject": {
"type": "string",
"format": "at-uri",
"description": "Subject AT-URI requested by the caller; matching ignores the subject CID."
},
"commentCount": {
"type": "integer",
"minimum": 0,
"description": "Number of current indexed comment attachments whose subjects include this URI; zero when none are indexed."
}
}
},
"output": {
"type": "object",
"required": [
"counts"
],
"properties": {
"counts": {
"type": "array",
"description": "One entry for each requested subject, in request order. Repeated input URIs produce repeated entries. Counts reflect current indexed state and may change between requests.",
"items": {
"type": "ref",
"ref": "#subjectCommentCount"
}
}
}
}
}
}
Loading
Loading