Skip to content
Closed
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
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,26 @@ All notable changes to the Fovea project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.5.4] - 2026-06-25

The 0.5.4 patch fixes project-scope and ownership stamping on video summaries and claims ([#181](https://github.com/parafovea/fovea/pull/181)). Project collaborators could not see a teammate's summary or add claims under it because summaries were persisted without their persona's project, and model-generated summaries and extracted claims were left unowned. Nothing is breaking; the API additively gains a `projectId` field on summary and claim responses.

### Fixed

#### Summaries and Claims Are Stamped With Their Persona's Project

- Every video-summary and claim write now stamps `projectId` from the persona (or the parent summary). Previously the interactive summary route (`server/src/routes/summaries.ts`), the summarization and claim-extraction workers (`server/src/queues/setup.ts`), and the auto-created empty summary (`server/src/repositories/ClaimRepository.ts`) all omitted it, so rows were born `projectId = NULL` and were invisible to every project collaborator except the creator — which `403`'d them at the parent-summary read gate when they tried to add claims, and hid the content from project-scoped queries. The summary update path also re-stamps the scope so a previously NULL-scoped row heals on its next save, and a migration backfills existing summaries and claims.

#### Model-Generated Summaries and Extracted Claims Are Owned

- The summarization and claim-extraction queue workers created rows without `createdBy`, leaving model-generated summaries and extracted claims owned by no one (readable only by an admin). The requesting user is now threaded through the queue payload and stamped as the owner on create.

### Added

#### projectId on Summary and Claim API Responses

- The `VideoSummary` and `Claim` API responses now include `projectId`, so clients can reflect a resource's project scope; its prior absence had helped the stamping defect go unnoticed.

## [0.5.3] - 2026-06-24

The 0.5.3 patch fixes a claims-workspace interaction bug ([#177](https://github.com/parafovea/fovea/pull/177)). No API shapes change and nothing is breaking.
Expand Down
2 changes: 1 addition & 1 deletion annotation-tool/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@fovea/annotation-tool",
"private": true,
"version": "0.5.3",
"version": "0.5.4",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
12 changes: 12 additions & 0 deletions annotation-tool/src/api/generated/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4104,6 +4104,7 @@ export interface paths {
processingTimeFusion?: number;
comment?: string | null;
createdBy?: string;
projectId?: string | null;
/** Format: date-time */
createdAt: string;
/** Format: date-time */
Expand Down Expand Up @@ -4188,6 +4189,7 @@ export interface paths {
processingTimeFusion?: number;
comment?: string | null;
createdBy?: string;
projectId?: string | null;
/** Format: date-time */
createdAt: string;
/** Format: date-time */
Expand Down Expand Up @@ -4324,6 +4326,7 @@ export interface paths {
processingTimeFusion?: number;
comment?: string | null;
createdBy?: string;
projectId?: string | null;
/** Format: date-time */
createdAt: string;
/** Format: date-time */
Expand Down Expand Up @@ -4496,6 +4499,7 @@ export interface paths {
processingTimeFusion?: number;
comment?: string | null;
createdBy?: string;
projectId?: string | null;
/** Format: date-time */
createdAt: string;
/** Format: date-time */
Expand Down Expand Up @@ -4638,6 +4642,7 @@ export interface paths {
processingTimeFusion?: number;
comment?: string | null;
createdBy?: string;
projectId?: string | null;
/** Format: date-time */
createdAt: string;
/** Format: date-time */
Expand Down Expand Up @@ -4744,6 +4749,7 @@ export interface paths {
processingTimeFusion?: number;
comment?: string | null;
createdBy?: string;
projectId?: string | null;
/** Format: date-time */
createdAt: string;
/** Format: date-time */
Expand Down Expand Up @@ -4854,6 +4860,7 @@ export interface paths {
metadata?: ("text" | "non-text")[] | null;
comment?: null | string;
createdBy?: null | string;
projectId?: null | string;
/** Format: date-time */
createdAt: string;
/** Format: date-time */
Expand Down Expand Up @@ -5010,6 +5017,7 @@ export interface paths {
metadata?: ("text" | "non-text")[] | null;
comment?: null | string;
createdBy?: null | string;
projectId?: null | string;
/** Format: date-time */
createdAt: string;
/** Format: date-time */
Expand Down Expand Up @@ -5139,6 +5147,7 @@ export interface paths {
metadata?: ("text" | "non-text")[] | null;
comment?: null | string;
createdBy?: null | string;
projectId?: null | string;
/** Format: date-time */
createdAt: string;
/** Format: date-time */
Expand Down Expand Up @@ -5292,6 +5301,7 @@ export interface paths {
metadata?: ("text" | "non-text")[] | null;
comment?: null | string;
createdBy?: null | string;
projectId?: null | string;
/** Format: date-time */
createdAt: string;
/** Format: date-time */
Expand Down Expand Up @@ -6032,6 +6042,7 @@ export interface paths {
metadata?: ("text" | "non-text")[] | null;
comment?: null | string;
createdBy?: null | string;
projectId?: null | string;
/** Format: date-time */
createdAt: string;
/** Format: date-time */
Expand Down Expand Up @@ -11469,6 +11480,7 @@ export interface components {
metadata?: ("text" | "non-text")[] | null;
comment?: null | string;
createdBy?: null | string;
projectId?: null | string;
/** Format: date-time */
createdAt: string;
/** Format: date-time */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* @file summary-project-scope.spec.ts
* @description End-to-end guard, through the running stack, that a video
* summary and the claims under it are stamped with their persona's project.
* A summary born with projectId = NULL is invisible to project collaborators
* and 403s their claim creation; this spec drives the real create routes and
* asserts the persisted project scope is returned on the API.
*
* The cross-user 403 itself is reproduced in the server integration test
* (test/integration/summary-claim-project-scope.test.ts); E2E worker users are
* system admins that bypass CASL, so here we verify the stamping that the
* authorization relies on.
*/

import { test, expect } from '../../fixtures/test-context.js'

test.describe('Summary project scope', () => {
test('stamps the persona project on summaries and claims created through the stack', async ({
page,
testUser,
testVideo,
}) => {
const api = page.request

// Create a project and a persona scoped to it.
const slug = `scope-${Date.now()}`
const projectRes = await api.post('/api/projects', {
data: { name: `Scope ${slug}`, slug },
})
expect(projectRes.status()).toBe(201)
const projectId = (await projectRes.json()).id

const personaRes = await api.post('/api/personas', {
data: {
name: `Project persona ${slug}`,
role: 'Analyst',
informationNeed: 'Project-scoped authoring',
projectId,
},
})
expect(personaRes.status()).toBe(201)
const personaId = (await personaRes.json()).id

// Create a summary under the project persona.
const summaryRes = await api.post('/api/summaries', {
data: {
videoId: testVideo.id,
personaId,
summary: [{ type: 'text', content: 'A red car drives through the intersection.' }],
},
})
expect(summaryRes.status()).toBe(201)
const summaryId = (await summaryRes.json()).id

// The persisted summary carries the persona's project, not NULL.
const readRes = await api.get(`/api/videos/${testVideo.id}/summaries/${personaId}`)
expect(readRes.status()).toBe(200)
expect((await readRes.json()).projectId).toBe(projectId)

// A claim created under it inherits the same project scope.
const claimRes = await api.post(`/api/summaries/${summaryId}/claims`, {
data: { summaryType: 'video', text: 'The car is red.', audio: ['speech'] },
})
expect(claimRes.status()).toBe(201)

const claimsRes = await api.get(`/api/summaries/${summaryId}/claims`)
expect(claimsRes.status()).toBe(200)
const claims = await claimsRes.json()
expect(claims.length).toBeGreaterThan(0)
expect(claims[0].projectId).toBe(projectId)
})
})
20 changes: 20 additions & 0 deletions docs/docs/project/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,26 @@ All notable changes to the Fovea project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.5.4] - 2026-06-25

The 0.5.4 patch fixes project-scope and ownership stamping on video summaries and claims ([#181](https://github.com/parafovea/fovea/pull/181)). Project collaborators could not see a teammate's summary or add claims under it because summaries were persisted without their persona's project, and model-generated summaries and extracted claims were left unowned. Nothing is breaking; the API additively gains a `projectId` field on summary and claim responses.

### Fixed

#### Summaries and Claims Are Stamped With Their Persona's Project

- Every video-summary and claim write now stamps `projectId` from the persona (or the parent summary). Previously the interactive summary route (`server/src/routes/summaries.ts`), the summarization and claim-extraction workers (`server/src/queues/setup.ts`), and the auto-created empty summary (`server/src/repositories/ClaimRepository.ts`) all omitted it, so rows were born `projectId = NULL` and were invisible to every project collaborator except the creator — which `403`'d them at the parent-summary read gate when they tried to add claims, and hid the content from project-scoped queries. The summary update path also re-stamps the scope so a previously NULL-scoped row heals on its next save, and a migration backfills existing summaries and claims.

#### Model-Generated Summaries and Extracted Claims Are Owned

- The summarization and claim-extraction queue workers created rows without `createdBy`, leaving model-generated summaries and extracted claims owned by no one (readable only by an admin). The requesting user is now threaded through the queue payload and stamped as the owner on create.

### Added

#### projectId on Summary and Claim API Responses

- The `VideoSummary` and `Claim` API responses now include `projectId`, so clients can reflect a resource's project scope; its prior absence had helped the stamping defect go unnoticed.

## [0.5.3] - 2026-06-24

The 0.5.3 patch fixes a claims-workspace interaction bug ([#177](https://github.com/parafovea/fovea/pull/177)). No API shapes change and nothing is breaking.
Expand Down
2 changes: 1 addition & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "docs",
"version": "0.5.3",
"version": "0.5.4",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
Expand Down
2 changes: 1 addition & 1 deletion model-service/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fovea-model-service",
"version": "0.5.3",
"version": "0.5.4",
"description": "AI model inference service for video annotation",
"scripts": {
"dev": "source venv/bin/activate && uvicorn src.main:app --reload --host 0.0.0.0 --port 8000",
Expand Down
2 changes: 1 addition & 1 deletion model-service/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "fovea-model-service"
version = "0.5.3"
version = "0.5.4"
description = "Model service for fovea video annotation tool"
requires-python = ">=3.12"
dependencies = [
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "fovea",
"private": true,
"version": "0.5.3",
"version": "0.5.4",
"description": "FOVEA - Flexible Ontology Visual Event Analyzer",
"packageManager": "pnpm@10.15.0",
"engines": {
Expand Down
Loading
Loading