Osy#betaRun it on Osyrin: osyrin.com

Reference / Agent

Work repositories — what an assignment knows, kept as items (WorkRepositories kit)

AddItem(a, new NewItem { Kind, Title, Content, Source, … }) · ReviseItem(item, new ItemRevision { … }, basedOn) · ReconcileItem(item, keep, why) · AcceptItem · RejectItem · NoteDisagreement · OpenKindTo · OpenItemTo · EraseItem

The `Osysharp.WorkRepositories` kit keeps an assignment's evidence, correspondence, findings, documents, proposals and decisions as items. An item is never overwritten: a change is a version, and each version says who or what wrote it — a person, an actor, the app itself — and what it was read from. Two contributors who start from the same version both stay. An item's kind and its standing are separate; disagreements stay open until a decision settles them; one kind can be restricted to fewer readers and kept for a set time; one item can be opened to somebody who is not on the assignment.

preview1 example compiled by CIagentskitassignmentsrepository

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 expired

Description#

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#

Related

Assignments — a piece of work owned until its outcome is reached (Assignments kit)

An assignment of the `Osysharp.Assignments` kit says who asked, the outcome wanted and what must be true for it to be…

Writing an agent — a class with a purpose, skills and an account of its own (Agents kit)

An agent is a class of the `Osysharp.Agents` kit: what it is for, the skills it holds, what it may spend. It runs as an…

The receipt and the record — what every piece of an agent's work leaves (Agents kit)

Every piece of work leaves one receipt, `AgentTask` — who asked, the account it ran as, what it cost against its limit…

Privacy for the agent family — agents, the model meter, assignments and work repositories under a person's request

An app that uses `Osysharp.Privacy` beside the agent kits gets a holder for each without writing one:…

[Actor] — a principal that code may run as

A class marked `[Actor]` declares a principal that code may run as: a robot, a nightly job, an integration, a digital…

[Attachment] — a file readable wherever its row is

`[Attachment]` on a `FileAsset` reference makes the file readable by exactly the people who may read the row holding it…

Agents (a class with a job, its own account, a budget and a record)

An agent is a class of the `Osysharp.Agents` kit: a purpose, the skills it holds and what it may spend. A skill is a…