Summary#
var scan = AddItem(a, new NewItem {
Kind = ItemKind.Evidence, Title = "Carrier scan, 6 March", Content = "Scanned at the Malmö depot: damaged, held.",
Source = new ItemSource { From = "The carrier's tracking page" },
});
ReviseItem(scan, new ItemRevision { Content = "…released on 9 March.", Note = "A second scan" }, basedOn: 1);
MeetWith(CriterionOf(a, "resolved"), scan, "The carrier's scan shows it delivered");Signature#
RepositoryItem AddItem(Assignment a, NewItem what) // Kind, Title, Content, ContentType, DataJson, File, Key, Source, Restricted
RepositoryItemVersion ReviseItem(RepositoryItem item, ItemRevision change, int basedOn)
RepositoryItemVersion ReconcileItem(RepositoryItem item, int keep, string why, ItemRevision? merged = null)
List<RepositoryItem> ItemsOf(Assignment a, ItemKind? kind = null) · ItemOf(a, key) · CurrentOf(item) · VersionsOf(item)
void AcceptItem(item, why = "") · RejectItem(item, why) · QuestionItem(item, why) · SupersedeItem(item, by)
void MeetWith(AssignmentCriterion criterion, RepositoryItem item, string metBy)
RepositoryDisagreement NoteDisagreement(first, second, about) · ResolveDisagreement(disagreement, decision, how)
void OpenKindTo(a, kind, person) · CloseKindTo · OpenItemTo(item, person) · CloseItemTo · RestrictItem(item, restricted)
void EraseItem(item, why) · List<RepositoryItem> ExpiredItems() · int EraseExpiredItems()
void KeepItemUntil(RepositoryItem item, DateTime? until) // policy ManagesWorkRepositories
int TakeContributorOffRepositories(Principal person, string why) // policy ManagesWorkRepositories
enum ItemKind { Input, Document, Extracted, Evidence, Correspondence, Finding, Proposal, Decision, Deliverable }
enum ItemStanding { Unverified, Accepted, Rejected, Superseded }
enum ContributorKind { Person, Actor, System }
enum RetainFrom { Added, AssignmentClosed }
app.WorkRepositories = new WorkRepositoriesSetup { ItemPath, Kinds = [ new KindRule { Kind, Restricted, RetainFor, From } ] };
policy ManagesWorkRepositories // the app answers: reads every item, erases what is expiredDescription#
What happens when an item is changed?#
A version is added; nothing is overwritten. basedOn is the number of the version the contributor had read. When
that is the version that stands, the new one stands. When somebody else's has come to stand in the meantime, the new
one is kept beside it, marked unreconciled, and the item counts it. ReconcileItem settles it: one of the versions
is chosen, or — with merged — a new version takes from several. Every version stays in the item's history.
Who wrote a version?#
By and AddedAs are read from who is acting — a signed-in person, a declared actor
whose block is running, or the app itself in a process or a handler — never taken from the caller. Where it came
from is the caller's to say: ItemSource carries the source in words, the row it was read from, and, for an agent,
the skill and its version, the model, the tool and the piece of work.
What is the difference between kind and standing?#
Kind is what the item is; standing is whether anybody has checked it. An agent's finding and a decision somebody in
charge made are both items — the first is Unverified until it is accepted. Whoever is in charge of the assignment
accepts and rejects. A rejected item is kept, and can no longer meet a criterion.
What if two items disagree?#
NoteDisagreement records it, and it stays open until somebody in charge settles it with an item of kind
Decision. It is read by whoever may read both items — what a disagreement is about says something of each side.
Who reads what?#
Whoever is on the assignment reads what is not restricted. A restricted item — its kind's rule, or Restricted on
the item — is read by whoever added it and by whoever the kind (OpenKindTo) or the item (OpenItemTo) was opened
to. OpenItemTo also reaches somebody who is not on the assignment: they read that item, every version of it, and
nothing else. A version is read by exactly whoever reads its item, and so is its file.
How long is an item kept?#
As long as the assignment, unless its kind's rule says RetainFor — counted from when it was added, or from when
the assignment closes (reopening stops the clock). ExpiredItems() lists what has outlived its time and
EraseExpiredItems() erases it. KeepItemUntil(item, until) sets the date from outside — a period the tenant
chose, a privacy kit's.
What does erasing an item leave?#
Its place in the record, and nothing it said. EraseItem(item, why) empties every version — content, data, file,
what changed (Note) and where it came from (From) — and why the item stands where it does; a disagreement it is
part of no longer says what it was about. The item, its kind, who added it and when, its versions' times, its
standing, and that it was erased, when and why, stay. Whoever is in charge of the assignment may, and whoever
ManagesWorkRepositories admits.
The assignment's activity named the item when it was added, revised or opened to somebody. Its title and notes come
out of those lines ("Document added: an item that was erased") when whoever erases may erase from an assignment's
activity — the app itself, or a person ErasesAssignments admits. Otherwise the lines stand
until such a person calls EraseWordsFromActivity.
Can a contributor be taken off what they added?#
TakeContributorOffRepositories(person, why) leaves every item and version they added, word for word, and makes
them nobody's: who added it reads Somebody no longer here. What was opened to them is closed, and a restricted
item they added is from then on read by whoever its kind or the item was opened to. An app that uses
Osysharp.Privacy writes neither call: Privacy for the agent family — agents, the model meter, assignments and work repositories under a person's request erases what is about a person who asks, takes a
contributor off, and erases each kind when its own period ends.
Examples#
app Hiring {
use Osysharp.WorkRepositories;
use Osysharp.Assignments;
use Osysharp.Llm;
use Osysharp.Identity;
use Osysharp.Storage;
use Osysharp.Observability;
use Osysharp.Ui;
model "model/**/*.osy";
}
using Osysharp.Assignments;
using Osysharp.Identity;
using Osysharp.WorkRepositories;
// What an interviewer thought of a candidate is read by fewer people than the rest.
app.WorkRepositories = new WorkRepositoriesSetup { Kinds = [ new KindRule { Kind = ItemKind.Finding, Restricted = true } ] };
[Role] enum HiringRole { Authenticator, Manager, Interviewer }
partial entity RoleGrant { security { allow read when IsAuthenticated; } }
policy IsManager => RoleGrant.Any(g => g.Member == user && g.Role == HiringRole.Manager);
policy OpensAssignments => IsManager;
entity Person : Principal {
security { allow read when IsAuthenticated || IsAuthenticator; allow create when IsAuthenticator; }
}
[TestFixture]
void Office() {
var hanna = new Person { Email = "[email protected]", DisplayName = "Hanna" };
var ivo = new Person { Email = "[email protected]", DisplayName = "Ivo" };
var lena = new Person { Email = "[email protected]", DisplayName = "Lena" };
var pia = new Person { Email = "[email protected]", DisplayName = "Pia" };
new RoleGrant { Member = hanna, Role = HiringRole.Manager };
new RoleGrant { Member = ivo, Role = HiringRole.Interviewer };
new RoleGrant { Member = lena, Role = HiringRole.Interviewer };
new RoleGrant { Member = pia, Role = HiringRole.Interviewer };
}
principal Hanna => Person.Single(p => p.Email == "[email protected]");
principal Ivo => Person.Single(p => p.Email == "[email protected]");
principal Lena => Person.Single(p => p.Email == "[email protected]");
principal Pia => Person.Single(p => p.Email == "[email protected]");
/// Open the hire, with both interviewers on it. Answers its id.
Guid OpenTheHire(Person first, Person second) {
var hire = OpenAssignment(new NewAssignment { Title = "Hire a consultant", Goal = "A consultant has accepted our offer.", Criteria = ["The decision is recorded"] });
AddParticipant(hire, first, ParticipantRole.Contributor);
AddParticipant(hire, second, ParticipantRole.Contributor);
return hire.Id;
}
Assignment Hire(Guid id) { return Assignment.Single(a => a.Id == id); }
RepositoryItem Item(Guid id) { return RepositoryItem.Single(i => i.Id == id); }
[Test(Office)]
void Two_contributors_change_one_item_and_neither_is_lost() {
Guid hire = Guid.Empty;
Guid notes = Guid.Empty;
runas (Hanna) { hire = OpenTheHire(Ivo, Lena); }
runas (Hanna) { notes = AddItem(Hire(hire), new NewItem { Kind = ItemKind.Input, Title = "Panel notes", Content = "Strong on rollouts." }).Id; }
// Both interviewers open version 1, and both write.
runas (Ivo) { ReviseItem(Item(notes), new ItemRevision { Content = "Strong on rollouts. Weak on migration." }, basedOn: 1); }
runas (Lena) { ReviseItem(Item(notes), new ItemRevision { Content = "Strong on rollouts. Led two migrations." }, basedOn: 1); }
runas (Hanna) {
var item = Item(notes);
Assert.Equal(2, item.Current); // Ivo's arrived first, and stands
Assert.Equal(1, item.Unreconciled); // Lena's is kept beside it
Assert.Equal(3, VersionsOf(item).Count);
Assert.Equal(Lena.Id, VersionsOf(item)[2].By!.Id);
Assert.Equal(ContributorKind.Person, VersionsOf(item)[2].AddedAs);
ReconcileItem(item, keep: 2, why: "Both are right.", merged: new ItemRevision { Content = "Strong on rollouts. Led two migrations; the second overran." });
Assert.Equal(4, Item(notes).Current);
Assert.Equal(0, Item(notes).Unreconciled);
Assert.Equal(4, VersionsOf(Item(notes)).Count); // nothing was overwritten
}
}
[Test(Office)]
void A_restricted_kind_and_an_item_opened_to_somebody_outside() {
Guid hire = Guid.Empty;
Guid role = Guid.Empty;
runas (Hanna) { hire = OpenTheHire(Ivo, Lena); }
runas (Hanna) { role = AddItem(Hire(hire), new NewItem { Kind = ItemKind.Input, Title = "The role", Content = "Senior consultant." }).Id; }
runas (Ivo) { AddItem(Hire(hire), new NewItem { Kind = ItemKind.Finding, Title = "Ivo's feedback", Content = "Would not hire." }); }
runas (Lena) {
AddItem(Hire(hire), new NewItem { Kind = ItemKind.Finding, Title = "Lena's feedback", Content = "Hire." });
// Each interviewer reads the role and their own feedback — not the other's.
Assert.Equal(["The role", "Lena's feedback"], ItemsOf(Hire(hire)).Select(i => i.Title).ToList());
}
runas (Hanna) {
Assert.Equal(1, ItemsOf(Hire(hire)).Count);
OpenKindTo(Hire(hire), ItemKind.Finding, Hanna);
Assert.Equal(3, ItemsOf(Hire(hire)).Count);
// Pia is not on the hire. She is handed the role — that item, and nothing else.
OpenItemTo(Item(role), Pia);
}
runas (Pia) {
Assert.Equal(["The role"], RepositoryItem.ToList().Select(i => i.Title).ToList());
Assert.Equal(0, Assignment.Count());
}
// An interviewer adds to the hire; what is taken as right is for whoever is in charge.
runas (Ivo) { Assert.Throws<NotAuthorized>(() => AcceptItem(Item(role))); }
runas (Hanna) {
AcceptItem(Item(role), "Agreed.");
MeetWith(Hire(hire).Criteria.Single(), Item(role), "The role is on file");
Assert.Equal(role, Hire(hire).Criteria.Single().Evidence);
}
}See also#
- Assignments — a piece of work owned until its outcome is reached (Assignments kit) — the assignment a repository belongs to, and its criteria
- Writing an agent — a class with a purpose, skills and an account of its own (Agents kit) — agents, whose findings land here as items
- The receipt and the record — what every piece of an agent's work leaves (Agents kit) — the record of an agent's steps, which an item's source points back to
- [Attachment] — a file readable wherever its row is — a version's file is read by exactly whoever reads the version